incyclist-devices 2.3.34 → 2.3.35

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.
@@ -10,13 +10,23 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.BleZwiftPlaySensor = void 0;
13
+ const zwift_hub_1 = require("../../../proto/zwift_hub");
13
14
  const sensor_1 = require("../../base/sensor");
14
15
  const utils_1 = require("../../utils");
15
16
  const crypto_1 = require("crypto");
16
17
  const events_1 = require("events");
17
18
  class BleZwiftPlaySensor extends sensor_1.TBleSensor {
18
19
  constructor(peripheral, props) {
19
- super(peripheral, props);
20
+ if (peripheral &&
21
+ 'startSensor' in peripheral && typeof peripheral.startSensor === 'function' &&
22
+ 'getPeripheral' in peripheral && typeof peripheral.getPeripheral === 'function') {
23
+ super(peripheral.getPeripheral(), props);
24
+ this.isFM = true;
25
+ }
26
+ else {
27
+ super(peripheral, props);
28
+ this.isFM = false;
29
+ }
20
30
  this.emitter = new events_1.EventEmitter();
21
31
  this.setInitialState();
22
32
  }
@@ -42,18 +52,66 @@ class BleZwiftPlaySensor extends sensor_1.TBleSensor {
42
52
  }
43
53
  onData(characteristic, data, isNotify) {
44
54
  const uuid = (0, utils_1.beautifyUUID)(characteristic).toLowerCase();
55
+ console.log('# data', uuid, data === null || data === void 0 ? void 0 : data.toString('hex'));
45
56
  if (uuid === '00000002-19ca-4651-86e5-fa29dcdd09d1') {
46
- this.onPlayMeasurement(data);
57
+ this.onMeasurement(data);
47
58
  }
48
59
  else if (uuid === '00000004-19ca-4651-86e5-fa29dcdd09d1') {
49
- this.onPairResponse(data);
60
+ this.onResponse(data);
50
61
  }
51
62
  else {
52
- console.log('data received ', isNotify ? 'N' : 'I', uuid, data.toString('hex'));
53
63
  }
54
64
  return true;
55
65
  }
56
- onPlayMeasurement(d) {
66
+ requestDataUpdate(dataId) {
67
+ return __awaiter(this, void 0, void 0, function* () {
68
+ yield this.sendHubRequest({ dataId });
69
+ });
70
+ }
71
+ setSimulationData(data) {
72
+ return __awaiter(this, void 0, void 0, function* () {
73
+ var _a, _b, _c, _d;
74
+ if (!this.isHubServiceActive) {
75
+ yield this.initHubService(false);
76
+ }
77
+ const crrx100000 = Math.round((_a = data === null || data === void 0 ? void 0 : data.crrx100000) !== null && _a !== void 0 ? _a : 5100);
78
+ const cWax10000 = Math.round((_b = data === null || data === void 0 ? void 0 : data.cWax10000) !== null && _b !== void 0 ? _b : 400);
79
+ const windx100 = Math.round((_c = data === null || data === void 0 ? void 0 : data.windx100) !== null && _c !== void 0 ? _c : 0);
80
+ const inclineX100 = Math.round((_d = data === null || data === void 0 ? void 0 : data.inclineX100) !== null && _d !== void 0 ? _d : 0);
81
+ const simulation = { inclineX100, crrx100000, cWax10000, windx100 };
82
+ yield this.sendHubCommand({ simulation });
83
+ yield this.requestDataUpdate(512);
84
+ });
85
+ }
86
+ setGearRatio(gearRatio) {
87
+ return __awaiter(this, void 0, void 0, function* () {
88
+ try {
89
+ console.log('# set gear ratio', gearRatio);
90
+ if (!this.isHubServiceActive) {
91
+ yield this.initHubService();
92
+ }
93
+ const gearRatioX10000 = Math.round(gearRatio * 10000);
94
+ const bikeWeightx100 = 10 * 100;
95
+ const riderWeightx100 = 75 * 100;
96
+ const command = {
97
+ physical: { bikeWeightx100, gearRatioX10000, riderWeightx100 }
98
+ };
99
+ yield this.sendHubCommand(command);
100
+ yield this.requestDataUpdate(512);
101
+ }
102
+ catch (err) {
103
+ console.log('# set gear ratio failed', err);
104
+ }
105
+ return gearRatio;
106
+ });
107
+ }
108
+ sendPlayCommand(id, command) {
109
+ return __awaiter(this, void 0, void 0, function* () {
110
+ const data = Buffer.concat([Buffer.from([id]), command]);
111
+ return yield this.write('00000003-19ca-4651-86e5-fa29dcdd09d1', data, { withoutResponse: true });
112
+ });
113
+ }
114
+ onMeasurement(d) {
57
115
  const data = Buffer.from(d);
58
116
  if ((data === null || data === void 0 ? void 0 : data.length) < 1) {
59
117
  console.log('Invalid click measurement data', data.toString('hex'));
@@ -62,84 +120,183 @@ class BleZwiftPlaySensor extends sensor_1.TBleSensor {
62
120
  const type = data.readUInt8(0);
63
121
  const message = data.subarray(1);
64
122
  if (type === 0x37) {
65
- this.onButtonMessage(message);
123
+ this.onClickButtonMessage(message);
66
124
  }
67
125
  else if (type === 0x19) {
68
126
  this.onPingMessage(message);
69
127
  }
128
+ else if (type === 0x42) {
129
+ console.log('# init confirmed');
130
+ }
131
+ else if (type === 0x03) {
132
+ this.onRidingData(message);
133
+ }
134
+ else if (type === 0x3c) {
135
+ this.onDeviceInformation(message);
136
+ }
70
137
  else {
71
138
  console.log('Unknown click measurement type', type, message.toString('hex'));
72
139
  this.emit('data', { raw: data.toString('hex') });
73
140
  }
74
141
  return true;
75
142
  }
76
- onButtonMessage(d) {
77
- var _a, _b;
78
- const message = Buffer.from(d);
143
+ initHubService() {
144
+ return __awaiter(this, arguments, void 0, function* (setSimulation = true) {
145
+ console.log('# init Hub Service');
146
+ if (this.isHubServiceActive)
147
+ return true;
148
+ if (!this.isPaired) {
149
+ yield this.pair();
150
+ }
151
+ if (!this.isSubscribed) {
152
+ yield this.subscribe();
153
+ this.isSubscribed = true;
154
+ }
155
+ return new Promise((done) => {
156
+ let timeout = setTimeout(() => {
157
+ done(false);
158
+ }, 2000);
159
+ this.once('hub-riding-data', () => {
160
+ if (timeout) {
161
+ clearTimeout(timeout);
162
+ timeout = undefined;
163
+ }
164
+ this.isHubServiceActive = true;
165
+ console.log('# init hub service completed');
166
+ if (setSimulation) {
167
+ this.setSimulationData().then(() => {
168
+ done(true);
169
+ })
170
+ .catch(() => {
171
+ done(true);
172
+ });
173
+ }
174
+ else {
175
+ done(true);
176
+ }
177
+ });
178
+ this.sendPlayCommand(0x41, Buffer.from([0x08, 0x05]))
179
+ .catch(err => {
180
+ if (timeout) {
181
+ clearTimeout(timeout);
182
+ timeout = undefined;
183
+ }
184
+ console.log('# init hub service timeout');
185
+ done(false);
186
+ this.logEvent({ message: 'could not init hub service', reason: err.message });
187
+ });
188
+ });
189
+ });
190
+ }
191
+ sendHubRequest(request) {
192
+ return __awaiter(this, void 0, void 0, function* () {
193
+ const message = Buffer.from(zwift_hub_1.HubRequest.toBinary(request));
194
+ console.log('# sending hub request', request, message);
195
+ this.logEvent({ mesage: 'send zwift hub request', request });
196
+ return yield this.sendPlayCommand(0x00, message);
197
+ });
198
+ }
199
+ sendHubCommand(command) {
200
+ return __awaiter(this, void 0, void 0, function* () {
201
+ const message = Buffer.from(zwift_hub_1.HubCommand.toBinary(command));
202
+ console.log('# sending hub command', command, message);
203
+ this.logEvent({ mesage: 'send zwift hub command', command });
204
+ return yield this.sendPlayCommand(0x04, message);
205
+ });
206
+ }
207
+ onRidingData(m) {
79
208
  try {
80
- if (message.readUInt8(0) === 0x8 && message.length === 4) {
81
- const value = Buffer.from(message.subarray(1));
82
- const messageStr = value.toString('hex');
83
- if (messageStr === this.prevClickMessage) {
84
- return;
209
+ this.tsLastRidingData = Date.now();
210
+ const data = zwift_hub_1.HubRidingData.fromBinary(m);
211
+ this.emit('hub-riding-data', data);
212
+ console.log('#riding data', data);
213
+ this.logEvent({ message: 'riding data received', power: data.power, cadence: data.cadence, Speed: data.speedX100 / 100, heartrate: data.hR, unknown1: data.unknown1, unknown2: data.unknown2 });
214
+ }
215
+ catch (err) {
216
+ this.logEvent({ message: 'Error', fn: 'onRidingData', error: err.message, stack: err.stack });
217
+ }
218
+ }
219
+ onDeviceInformation(m) {
220
+ try {
221
+ const envelope = zwift_hub_1.DeviceDataEnvelope.fromBinary(m);
222
+ const { messageType, payload } = envelope;
223
+ if (messageType < 16) {
224
+ const deviceInfo = zwift_hub_1.DeviceInformationContent.fromBinary(payload);
225
+ console.log('# deviceInfo', deviceInfo);
226
+ this.emit('hub-device-info', deviceInfo);
227
+ this.logEvent({ message: 'hub device info update', deviceInfo });
228
+ }
229
+ else if (messageType >= 512 && messageType < 526) {
230
+ const si = zwift_hub_1.DeviceSettings.fromBinary(payload);
231
+ console.log('# settings', si === null || si === void 0 ? void 0 : si.subContent);
232
+ this.emit('hub-settings', si === null || si === void 0 ? void 0 : si.subContent);
233
+ this.logEvent({ message: 'hub settings update', settings: si === null || si === void 0 ? void 0 : si.subContent });
234
+ }
235
+ }
236
+ catch (err) {
237
+ let payload = 'unknown';
238
+ try {
239
+ payload = m.toString('hex');
240
+ }
241
+ catch (_a) { }
242
+ this.logEvent({ message: 'Error', fn: 'onRidingData', error: err.message, stack: err.stack, payload });
243
+ }
244
+ }
245
+ onClickButtonMessage(d) {
246
+ try {
247
+ const message = Buffer.from(d);
248
+ const messageStr = message.toString('hex');
249
+ if (messageStr === this.prevClickMessage) {
250
+ return;
251
+ }
252
+ const status = zwift_hub_1.ClickKeyPadStatus.fromBinary(message);
253
+ if (status.buttonPlus === zwift_hub_1.PlayButtonStatus.OFF) {
254
+ const prev = Object.assign({}, this.upState);
255
+ this.upState = { pressed: false, timestamp: Date.now() };
256
+ if (prev.pressed) {
257
+ this.emit('key-pressed', { key: 'up', duration: this.upState.timestamp - prev.timestamp, deviceType: this.deviceType });
85
258
  }
86
- switch (messageStr) {
87
- case '001001':
88
- this.upState = { pressed: true, timestamp: Date.now() };
89
- break;
90
- case '011001':
91
- if ((_a = this.upState) === null || _a === void 0 ? void 0 : _a.pressed) {
92
- const prev = Object.assign({}, this.upState);
93
- this.upState = { pressed: false, timestamp: Date.now() };
94
- this.emit('key-pressed', { key: 'up', duration: this.upState.timestamp - prev.timestamp, deviceType: this.deviceType });
95
- }
96
- else if ((_b = this.downState) === null || _b === void 0 ? void 0 : _b.pressed) {
97
- const prev = Object.assign({}, this.downState);
98
- this.downState = { pressed: false, timestamp: Date.now() };
99
- this.emit('key-pressed', { key: 'down', duration: this.downState.timestamp - prev.timestamp, deviceType: this.deviceType });
100
- }
101
- else {
102
- this.upState = { pressed: false, timestamp: Date.now() };
103
- this.downState = { pressed: false, timestamp: Date.now() };
104
- }
105
- break;
106
- case '011000':
107
- this.downState = { pressed: true, timestamp: Date.now() };
108
- break;
259
+ }
260
+ else {
261
+ this.upState = { pressed: true, timestamp: Date.now() };
262
+ }
263
+ if (status.buttonMinus === zwift_hub_1.PlayButtonStatus.OFF) {
264
+ const prev = Object.assign({}, this.downState);
265
+ this.downState = { pressed: false, timestamp: Date.now() };
266
+ if (prev.pressed) {
267
+ this.emit('key-pressed', { key: 'down', duration: this.downState.timestamp - prev.timestamp, deviceType: this.deviceType });
109
268
  }
110
- this.prevClickMessage = messageStr;
111
269
  }
112
270
  else {
113
- this.logEvent({ message: 'Click measurement received', raw: message.toString('hex') });
271
+ this.downState = { pressed: true, timestamp: Date.now() };
114
272
  }
273
+ this.prevClickMessage = messageStr;
115
274
  }
116
275
  catch (err) {
117
276
  this.logEvent({ message: 'error', fn: 'onButtonMessage', error: err.message, stack: err.stack });
118
277
  }
119
278
  }
120
- onPingMessage(d) {
279
+ onPingMessage(message) {
280
+ const idle = zwift_hub_1.Idle.fromBinary(message);
281
+ console.log('# ping message', idle);
121
282
  this.emit('data', { deviceType: this.deviceType, paired: this.paired, alive: true, ts: Date.now() });
122
283
  }
123
- onPairResponse(d) {
284
+ onResponse(d) {
124
285
  const data = Buffer.from(d);
125
- const len = data.length;
126
- if (len === 6 && data.toString() === 'RideOn') {
127
- this.encrypted = false;
128
- this.paired = true;
129
- this.emitter.emit('paired');
130
- try {
131
- this.emit('data', { deviceType: this.deviceType, paired: true, ts: Date.now() });
132
- }
133
- catch (err) {
134
- this.logEvent({ message: 'error', fn: 'onPairResponse', error: err.message, stack: err.stack });
135
- }
286
+ if ((data === null || data === void 0 ? void 0 : data.length) < 1) {
287
+ console.log('Invalid response data', data.toString('hex'));
288
+ return false;
289
+ }
290
+ const type = data.readUInt8(0);
291
+ const message = data.subarray(1);
292
+ if (type === 0x3c) {
293
+ this.onDeviceInformation(message);
136
294
  }
137
- else if (len > 8 && Buffer.from(data.subarray(0, 6)).toString() === 'RideOn') {
138
- const message = Buffer.from(data.subarray(6, 2)).toString('hex');
139
- if (message === '0900') {
140
- this.encrypted = true;
295
+ else {
296
+ const len = data.length;
297
+ if (len === 6 && data.toString() === 'RideOn') {
298
+ this.encrypted = false;
141
299
  this.paired = true;
142
- this.deviceKey = data.subarray(8);
143
300
  this.emitter.emit('paired');
144
301
  try {
145
302
  this.emit('data', { deviceType: this.deviceType, paired: true, ts: Date.now() });
@@ -148,11 +305,26 @@ class BleZwiftPlaySensor extends sensor_1.TBleSensor {
148
305
  this.logEvent({ message: 'error', fn: 'onPairResponse', error: err.message, stack: err.stack });
149
306
  }
150
307
  }
151
- else {
152
- this.logEvent({ message: 'Pairing failed! ', reason: 'unknown message', raw: message });
308
+ else if (len > 8 && Buffer.from(data.subarray(0, 6)).toString() === 'RideOn') {
309
+ const message = Buffer.from(data.subarray(6, 2)).toString('hex');
310
+ if (message === '0900') {
311
+ this.encrypted = true;
312
+ this.paired = true;
313
+ this.deviceKey = data.subarray(8);
314
+ this.emitter.emit('paired');
315
+ try {
316
+ this.emit('data', { deviceType: this.deviceType, paired: true, ts: Date.now() });
317
+ }
318
+ catch (err) {
319
+ this.logEvent({ message: 'error', fn: 'onPairResponse', error: err.message, stack: err.stack });
320
+ }
321
+ }
322
+ else {
323
+ this.logEvent({ message: 'Pairing failed! ', reason: 'unknown message', raw: message });
324
+ }
153
325
  }
326
+ return true;
154
327
  }
155
- return true;
156
328
  }
157
329
  read(characteristic_1) {
158
330
  const _super = Object.create(null, {
@@ -172,6 +344,8 @@ class BleZwiftPlaySensor extends sensor_1.TBleSensor {
172
344
  pair() {
173
345
  return __awaiter(this, void 0, void 0, function* () {
174
346
  var _a, _b;
347
+ if (this.isPaired)
348
+ return true;
175
349
  let manufacturerData;
176
350
  try {
177
351
  if (this.peripheral.getManufacturerData) {
@@ -197,7 +371,7 @@ class BleZwiftPlaySensor extends sensor_1.TBleSensor {
197
371
  this.logEvent({ message: 'Play protocol pairing info', deviceType: this.deviceType, encrypted: this.encrypted, manufacturerData });
198
372
  let message;
199
373
  if (!this.encrypted) {
200
- message = Buffer.from('RideOn');
374
+ message = this.isFM ? Buffer.concat([Buffer.from('RideOn'), Buffer.from([0x02, 0x01])]) : Buffer.from('RideOn');
201
375
  }
202
376
  else {
203
377
  const { publicKey, privateKey } = this.createKeyPair();
@@ -206,10 +380,12 @@ class BleZwiftPlaySensor extends sensor_1.TBleSensor {
206
380
  message = Buffer.concat([Buffer.from('RideOn'), Buffer.from([0x01, 0x02]), this.publicKey]);
207
381
  }
208
382
  yield this.write((0, utils_1.fullUUID)('00000003-19ca-4651-86e5-fa29dcdd09d1'), message, { withoutResponse: true });
383
+ this.isPaired = true;
209
384
  return true;
210
385
  }
211
386
  catch (err) {
212
387
  this.logEvent({ message: 'error', fn: 'pair', error: err.message, stack: err.stack });
388
+ this.isPaired = false;
213
389
  return false;
214
390
  }
215
391
  });
@@ -0,0 +1,6 @@
1
+ export type HubRequest = {
2
+ DataId: number;
3
+ };
4
+ export type IHubHelper = {
5
+ createHubRequest(request: HubRequest): any;
6
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -18,9 +18,12 @@ export declare class DirectConnectPeripheral implements IBlePeripheral {
18
18
  protected eventEmitter: EventEmitter<[never]>;
19
19
  protected subscribed: Array<string>;
20
20
  protected onDisconnectHandler: () => void;
21
+ protected discoveredServiceUUIds: Array<string>;
21
22
  constructor(announcement: MulticastDnsAnnouncement);
22
23
  getInfo(): BleDeviceIdentifier;
23
24
  get services(): BleService[];
25
+ getAnnouncedServices(): string[];
26
+ getDiscoveredServices(): string[];
24
27
  connect(): Promise<boolean>;
25
28
  disconnect(connectionLost?: boolean): Promise<boolean>;
26
29
  isConnected(): boolean;
@@ -51,6 +51,13 @@ class DirectConnectPeripheral {
51
51
  const services = (_b = (_a = this.announcement) === null || _a === void 0 ? void 0 : _a.serviceUUIDs) !== null && _b !== void 0 ? _b : [];
52
52
  return services.map(s => ({ uuid: s }));
53
53
  }
54
+ getAnnouncedServices() {
55
+ var _a;
56
+ return (_a = this.announcement) === null || _a === void 0 ? void 0 : _a.serviceUUIDs.map(s => (0, utils_1.beautifyUUID)(s));
57
+ }
58
+ getDiscoveredServices() {
59
+ return this.discoveredServiceUUIds;
60
+ }
54
61
  connect() {
55
62
  return __awaiter(this, void 0, void 0, function* () {
56
63
  if (this.isConnected())
@@ -104,6 +111,7 @@ class DirectConnectPeripheral {
104
111
  return [];
105
112
  }
106
113
  this.logEvent({ message: 'DiscoverServices response', path: this.getPath(), rc, uuids, raw: response.toString('hex') });
114
+ this.discoveredServiceUUIds = res.body.serviceDefinitions.map(s => (0, utils_1.beautifyUUID)(s.serviceUUID));
107
115
  return res.body.serviceDefinitions.map(s => s.serviceUUID);
108
116
  }
109
117
  catch (err) {
@@ -50,7 +50,6 @@ export default class SmartTrainerCyclingMode extends PowerBasedCyclingModeBase i
50
50
  updateData(bikeData: IncyclistBikeData, log?: boolean): IncyclistBikeData;
51
51
  getData(): Partial<IncyclistBikeData>;
52
52
  sendBikeUpdate(incoming: UpdateRequest): UpdateRequest;
53
- protected setInitialGear(data: IncyclistBikeData): void;
54
53
  protected getGearString(): string;
55
54
  }
56
55
  export {};
@@ -51,6 +51,12 @@ class SmartTrainerCyclingMode extends power_base_1.default {
51
51
  this.initLogger('SmartTrainerMode');
52
52
  }
53
53
  getBikeInitRequest() {
54
+ const virtshiftMode = this.getVirtualShiftMode();
55
+ if (virtshiftMode === 'Adapter') {
56
+ this.gear = this.getSetting('startGear');
57
+ const gearRatio = this.gearRatios[this.gear - 1];
58
+ return { slope: 0, gearRatio };
59
+ }
54
60
  this.prevRequest = { slope: 0 };
55
61
  return { slope: 0 };
56
62
  }
@@ -66,13 +72,19 @@ class SmartTrainerCyclingMode extends power_base_1.default {
66
72
  getConfig() {
67
73
  const config = super.getConfig();
68
74
  let virtshift = config.properties.find(p => p.key === 'virtshift');
75
+ let startGear = config.properties.find(p => p.key === 'startGear');
69
76
  if (!virtshift) {
70
- virtshift = { key: 'virtshift', name: 'Virtual Shifting', description: 'Enable virtual shifting', type: types_1.CyclingModeProperyType.SingleSelect, options: ['Disabled', 'Enabled', 'Mixed'], default: 'Disabled' };
77
+ virtshift = { key: 'virtshift', name: 'Virtual Shifting', description: 'Enable virtual shifting', type: types_1.CyclingModeProperyType.SingleSelect, options: ['Disabled', 'Incyclist', 'Mixed'], default: 'Disabled' };
71
78
  config.properties.push(virtshift);
72
79
  }
73
80
  if (this.adapter.supportsVirtualShifting()) {
74
81
  virtshift.default = 'Enabled';
75
- virtshift.options = ['Disabled', 'Enabled'];
82
+ virtshift.options = ['Disabled', 'Incyclist', 'SmartTrainer', 'Mixed'];
83
+ virtshift.default = 'SmartTrainer';
84
+ }
85
+ if (!startGear) {
86
+ startGear = { key: 'startGear', name: 'Initial Gear', description: 'Initial Gear', type: types_1.CyclingModeProperyType.Integer, default: 10, min: 1, max: 24, condition: (s) => (s === null || s === void 0 ? void 0 : s.virtshift) === 'Incyclist' || (s === null || s === void 0 ? void 0 : s.virtshift) === 'SmartTrainer' };
87
+ config.properties.push(startGear);
76
88
  }
77
89
  return config;
78
90
  }
@@ -170,7 +182,8 @@ class SmartTrainerCyclingMode extends power_base_1.default {
170
182
  case 'Simulated':
171
183
  if (request.gearDelta !== undefined) {
172
184
  if (this.gear === undefined) {
173
- this.gear = 10 + request.gearDelta;
185
+ const initialGear = this.getSetting('startGear');
186
+ this.gear = initialGear + request.gearDelta;
174
187
  }
175
188
  else {
176
189
  this.gear += request.gearDelta;
@@ -192,6 +205,47 @@ class SmartTrainerCyclingMode extends power_base_1.default {
192
205
  }
193
206
  break;
194
207
  case 'Adapter':
208
+ if (request.gearRatio !== undefined) {
209
+ newRequest.gearRatio = request.gearRatio;
210
+ if (this.gear === undefined) {
211
+ const requestedRatio = request.gearRatio;
212
+ let closestIndex = 0;
213
+ let minDiff = Math.abs(this.gearRatios[0] - requestedRatio);
214
+ for (let i = 1; i < this.gearRatios.length; i++) {
215
+ const diff = Math.abs(this.gearRatios[i] - requestedRatio);
216
+ if (diff < minDiff) {
217
+ minDiff = diff;
218
+ closestIndex = i;
219
+ }
220
+ }
221
+ this.gear = closestIndex + 1;
222
+ }
223
+ }
224
+ else if (request.gearDelta !== undefined) {
225
+ if (this.gear === undefined) {
226
+ const initialGear = this.getSetting('startGear');
227
+ this.gear = initialGear + request.gearDelta;
228
+ }
229
+ else {
230
+ this.gear += request.gearDelta;
231
+ }
232
+ if (this.gear < 1) {
233
+ this.gear = 1;
234
+ }
235
+ if (this.gear > this.gearRatios.length) {
236
+ this.gear = this.gearRatios.length;
237
+ }
238
+ delete request.gearDelta;
239
+ newRequest.gearRatio = this.gearRatios[this.gear];
240
+ }
241
+ else {
242
+ if (this.gear === undefined) {
243
+ const initialGear = this.getSetting('startGear');
244
+ this.gear = initialGear + request.gearDelta;
245
+ }
246
+ newRequest.gearRatio = this.gearRatios[this.gear];
247
+ }
248
+ break;
195
249
  case 'Disabled':
196
250
  default:
197
251
  break;
@@ -218,17 +272,22 @@ class SmartTrainerCyclingMode extends power_base_1.default {
218
272
  if (virtshiftMode === 'Disabled') {
219
273
  return 'Disabled';
220
274
  }
221
- if (this.adapter.supportsVirtualShifting()) {
222
- if (virtshiftMode === 'Enabled') {
223
- return 'Adapter';
224
- }
275
+ else if (virtshiftMode === 'Incyclist') {
276
+ return 'Simulated';
277
+ }
278
+ else if (virtshiftMode === 'SmartTrainer') {
279
+ return 'Adapter';
280
+ }
281
+ else if (virtshiftMode === 'Mixed') {
282
+ return 'SlopeDelta';
225
283
  }
226
- else {
227
- return virtshiftMode === 'Mixed' ? 'SlopeDelta' : 'Simulated';
284
+ else if (virtshiftMode === 'Enabled') {
285
+ return this.adapter.supportsVirtualShifting() ? 'Adapter' : 'Simulated';
228
286
  }
287
+ return 'Disabled';
229
288
  }
230
289
  updateData(bikeData, log) {
231
- var _a, _b, _c, _d;
290
+ var _a, _b, _c, _d, _e;
232
291
  const prev = Object.assign({}, this.data);
233
292
  const data = super.updateData(bikeData, log);
234
293
  const mode = this.getVirtualShiftMode();
@@ -241,14 +300,14 @@ class SmartTrainerCyclingMode extends power_base_1.default {
241
300
  this.tsStart = Date.now();
242
301
  }
243
302
  if (this.gear === undefined && this.tsStart && data.power > 0 && (Date.now() - this.tsStart > 3000)) {
244
- this.setInitialGear(data);
303
+ this.gear = (_a = this.getSetting('startGear')) !== null && _a !== void 0 ? _a : 0;
245
304
  data.gearStr = this.getGearString();
246
305
  }
247
306
  else if (this.gear !== undefined) {
248
307
  if (prev.power !== data.power || prev.pedalRpm !== data.pedalRpm) {
249
308
  virtualSpeed = (0, calculations_1.calculateVirtualSpeed)(data.pedalRpm, this.gearRatios[this.gear - 1]);
250
- const m = (_b = (_a = this.adapter) === null || _a === void 0 ? void 0 : _a.getWeight()) !== null && _b !== void 0 ? _b : 85;
251
- this.simPower = calculations_1.default.calculatePower(m, virtualSpeed, (_d = (_c = this.simSlope) !== null && _c !== void 0 ? _c : data.slope) !== null && _d !== void 0 ? _d : 0);
309
+ const m = (_c = (_b = this.adapter) === null || _b === void 0 ? void 0 : _b.getWeight()) !== null && _c !== void 0 ? _c : 85;
310
+ this.simPower = calculations_1.default.calculatePower(m, virtualSpeed, (_e = (_d = this.simSlope) !== null && _d !== void 0 ? _d : data.slope) !== null && _e !== void 0 ? _e : 0);
252
311
  this.verifySimPower();
253
312
  }
254
313
  }
@@ -279,16 +338,6 @@ class SmartTrainerCyclingMode extends power_base_1.default {
279
338
  }
280
339
  return newRequest;
281
340
  }
282
- setInitialGear(data) {
283
- var _a, _b;
284
- const m = (_b = (_a = this.adapter) === null || _a === void 0 ? void 0 : _a.getWeight()) !== null && _b !== void 0 ? _b : 85;
285
- const virtualSpeeds = this.gearRatios.map(r => (0, calculations_1.calculateVirtualSpeed)(data.pedalRpm, r));
286
- const requiredPowers = virtualSpeeds.map(v => { var _a; return calculations_1.default.calculatePower(m, v, (_a = data.slope) !== null && _a !== void 0 ? _a : 0); });
287
- const deltas = requiredPowers.map((p, i) => ({ gear: i + 1, delta: Math.abs(p - data.power) }));
288
- deltas.sort((a, b) => a.delta - b.delta);
289
- this.gear = deltas[0].gear;
290
- this.logger.logEvent({ message: 'set initial gear', gear: this.gear, m, rpm: data.pedalRpm, power: data.power });
291
- }
292
341
  getGearString() {
293
342
  const mode = this.getVirtualShiftMode();
294
343
  if (mode === "Disabled")
@@ -10,6 +10,7 @@ export type UpdateRequest = {
10
10
  refresh?: boolean;
11
11
  init?: boolean;
12
12
  gear?: number;
13
+ gearRatio?: number;
13
14
  forced?: boolean;
14
15
  };
15
16
  export declare enum CyclingModeProperyType {
@@ -29,6 +30,7 @@ export type CyclingModeProperty = {
29
30
  max?: number;
30
31
  default?: any;
31
32
  options?: any[];
33
+ condition?: (setting: any) => boolean;
32
34
  };
33
35
  export type Settings = {
34
36
  [key: string]: any;