incyclist-devices 2.3.34 → 2.3.36

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,69 @@ 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 : 400);
78
+ const cWax10000 = Math.round((_b = data === null || data === void 0 ? void 0 : data.cWax10000) !== null && _b !== void 0 ? _b : 5100);
79
+ const windx100 = Math.round((_c = data === null || data === void 0 ? void 0 : data.windx100) !== null && _c !== void 0 ? _c : 0);
80
+ let inclineX100 = Math.round((_d = data === null || data === void 0 ? void 0 : data.inclineX100) !== null && _d !== void 0 ? _d : 1);
81
+ if (inclineX100 === 0 || Number.isNaN(inclineX100))
82
+ inclineX100 = 1;
83
+ const simulation = { inclineX100, crrx100000, cWax10000, windx100 };
84
+ console.log('# set simulation data', simulation);
85
+ yield this.sendHubCommand({ simulation });
86
+ yield this.requestDataUpdate(512);
87
+ });
88
+ }
89
+ setGearRatio(gearRatio) {
90
+ return __awaiter(this, void 0, void 0, function* () {
91
+ try {
92
+ console.log('# set gear ratio', gearRatio);
93
+ if (!this.isHubServiceActive) {
94
+ yield this.initHubService();
95
+ }
96
+ const gearRatioX10000 = Math.round(gearRatio * 10000);
97
+ const bikeWeightx100 = 10 * 100;
98
+ const riderWeightx100 = 75 * 100;
99
+ const command = {
100
+ physical: { bikeWeightx100, gearRatioX10000, riderWeightx100 }
101
+ };
102
+ yield this.sendHubCommand(command);
103
+ yield this.requestDataUpdate(512);
104
+ }
105
+ catch (err) {
106
+ console.log('# set gear ratio failed', err);
107
+ }
108
+ return gearRatio;
109
+ });
110
+ }
111
+ sendPlayCommand(id, command) {
112
+ return __awaiter(this, void 0, void 0, function* () {
113
+ const data = Buffer.concat([Buffer.from([id]), command]);
114
+ return yield this.write('00000003-19ca-4651-86e5-fa29dcdd09d1', data, { withoutResponse: true });
115
+ });
116
+ }
117
+ onMeasurement(d) {
57
118
  const data = Buffer.from(d);
58
119
  if ((data === null || data === void 0 ? void 0 : data.length) < 1) {
59
120
  console.log('Invalid click measurement data', data.toString('hex'));
@@ -62,84 +123,190 @@ class BleZwiftPlaySensor extends sensor_1.TBleSensor {
62
123
  const type = data.readUInt8(0);
63
124
  const message = data.subarray(1);
64
125
  if (type === 0x37) {
65
- this.onButtonMessage(message);
126
+ this.onClickButtonMessage(message);
66
127
  }
67
128
  else if (type === 0x19) {
68
129
  this.onPingMessage(message);
69
130
  }
131
+ else if (type === 0x42) {
132
+ console.log('# init confirmed');
133
+ }
134
+ else if (type === 0x03) {
135
+ this.onRidingData(message);
136
+ }
137
+ else if (type === 0x3c) {
138
+ this.onDeviceInformation(message);
139
+ }
70
140
  else {
71
141
  console.log('Unknown click measurement type', type, message.toString('hex'));
72
142
  this.emit('data', { raw: data.toString('hex') });
73
143
  }
74
144
  return true;
75
145
  }
76
- onButtonMessage(d) {
77
- var _a, _b;
78
- const message = Buffer.from(d);
146
+ initHubService() {
147
+ return __awaiter(this, arguments, void 0, function* (setSimulation = true) {
148
+ if (!this.isHubServiceActive && this.initHubServicePromise !== undefined) {
149
+ yield this.initHubServicePromise;
150
+ }
151
+ if (this.isHubServiceActive)
152
+ return true;
153
+ this.logEvent({ message: 'init hub service', paired: this.paired, subscribed: this.isHubServiceSubscribed });
154
+ console.log('# init Hub Service');
155
+ if (!this.isHubServicePaired) {
156
+ yield this.pair();
157
+ }
158
+ if (!this.isHubServiceSubscribed) {
159
+ yield this.subscribe();
160
+ this.isHubServiceSubscribed = true;
161
+ }
162
+ this.initHubServicePromise = new Promise((done) => {
163
+ let timeout = setTimeout(() => {
164
+ done(false);
165
+ }, 2000);
166
+ this.once('hub-riding-data', () => {
167
+ if (timeout) {
168
+ clearTimeout(timeout);
169
+ timeout = undefined;
170
+ }
171
+ this.isHubServiceActive = true;
172
+ console.log('# init hub service completed');
173
+ this.logEvent({ message: 'init hub service done', paired: this.paired, subscribed: this.isHubServiceSubscribed });
174
+ if (setSimulation) {
175
+ this.logEvent({ message: 'hub: send initial simulation data' });
176
+ this.setSimulationData().then(() => {
177
+ done(true);
178
+ })
179
+ .catch(() => {
180
+ done(true);
181
+ });
182
+ }
183
+ else {
184
+ done(true);
185
+ }
186
+ });
187
+ this.sendPlayCommand(0x41, Buffer.from([0x08, 0x05]))
188
+ .catch(err => {
189
+ if (timeout) {
190
+ clearTimeout(timeout);
191
+ timeout = undefined;
192
+ }
193
+ console.log('# init hub service timeout');
194
+ done(false);
195
+ this.logEvent({ message: 'could not init hub service', reason: err.message });
196
+ });
197
+ });
198
+ return this.initHubServicePromise;
199
+ });
200
+ }
201
+ sendHubRequest(request) {
202
+ return __awaiter(this, void 0, void 0, function* () {
203
+ const message = Buffer.from(zwift_hub_1.HubRequest.toBinary(request));
204
+ console.log('# sending hub request', request, message);
205
+ this.logEvent({ mesage: 'send zwift hub request', request });
206
+ return yield this.sendPlayCommand(0x00, message);
207
+ });
208
+ }
209
+ sendHubCommand(command) {
210
+ return __awaiter(this, void 0, void 0, function* () {
211
+ const message = Buffer.from(zwift_hub_1.HubCommand.toBinary(command));
212
+ console.log('# sending hub command', command, message);
213
+ this.logEvent({ mesage: 'send zwift hub command', command });
214
+ return yield this.sendPlayCommand(0x04, message);
215
+ });
216
+ }
217
+ onRidingData(m) {
79
218
  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;
219
+ this.tsLastRidingData = Date.now();
220
+ const data = zwift_hub_1.HubRidingData.fromBinary(m);
221
+ this.emit('hub-riding-data', data);
222
+ console.log('#riding data', data);
223
+ 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 });
224
+ }
225
+ catch (err) {
226
+ this.logEvent({ message: 'Error', fn: 'onRidingData', error: err.message, stack: err.stack });
227
+ }
228
+ }
229
+ onDeviceInformation(m) {
230
+ try {
231
+ const envelope = zwift_hub_1.DeviceDataEnvelope.fromBinary(m);
232
+ const { messageType, payload } = envelope;
233
+ if (messageType < 16) {
234
+ const deviceInfo = zwift_hub_1.DeviceInformationContent.fromBinary(payload);
235
+ console.log('# deviceInfo', deviceInfo);
236
+ this.emit('hub-device-info', deviceInfo);
237
+ this.logEvent({ message: 'hub device info update', deviceInfo });
238
+ }
239
+ else if (messageType >= 512 && messageType < 526) {
240
+ const si = zwift_hub_1.DeviceSettings.fromBinary(payload);
241
+ console.log('# settings', si === null || si === void 0 ? void 0 : si.subContent);
242
+ this.emit('hub-settings', si === null || si === void 0 ? void 0 : si.subContent);
243
+ this.logEvent({ message: 'hub settings update', settings: si === null || si === void 0 ? void 0 : si.subContent });
244
+ }
245
+ }
246
+ catch (err) {
247
+ let payload = 'unknown';
248
+ try {
249
+ payload = m.toString('hex');
250
+ }
251
+ catch (_a) { }
252
+ this.logEvent({ message: 'Error', fn: 'onRidingData', error: err.message, stack: err.stack, payload });
253
+ }
254
+ }
255
+ onClickButtonMessage(d) {
256
+ try {
257
+ const message = Buffer.from(d);
258
+ const messageStr = message.toString('hex');
259
+ if (messageStr === this.prevClickMessage) {
260
+ return;
261
+ }
262
+ const status = zwift_hub_1.ClickKeyPadStatus.fromBinary(message);
263
+ if (status.buttonPlus === zwift_hub_1.PlayButtonStatus.OFF) {
264
+ const prev = Object.assign({}, this.upState);
265
+ this.upState = { pressed: false, timestamp: Date.now() };
266
+ if (prev.pressed) {
267
+ this.emit('key-pressed', { key: 'up', duration: this.upState.timestamp - prev.timestamp, deviceType: this.deviceType });
85
268
  }
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;
269
+ }
270
+ else {
271
+ this.upState = { pressed: true, timestamp: Date.now() };
272
+ }
273
+ if (status.buttonMinus === zwift_hub_1.PlayButtonStatus.OFF) {
274
+ const prev = Object.assign({}, this.downState);
275
+ this.downState = { pressed: false, timestamp: Date.now() };
276
+ if (prev.pressed) {
277
+ this.emit('key-pressed', { key: 'down', duration: this.downState.timestamp - prev.timestamp, deviceType: this.deviceType });
109
278
  }
110
- this.prevClickMessage = messageStr;
111
279
  }
112
280
  else {
113
- this.logEvent({ message: 'Click measurement received', raw: message.toString('hex') });
281
+ this.downState = { pressed: true, timestamp: Date.now() };
114
282
  }
283
+ this.prevClickMessage = messageStr;
115
284
  }
116
285
  catch (err) {
117
286
  this.logEvent({ message: 'error', fn: 'onButtonMessage', error: err.message, stack: err.stack });
118
287
  }
119
288
  }
120
- onPingMessage(d) {
289
+ onPingMessage(message) {
290
+ const idle = zwift_hub_1.Idle.fromBinary(message);
291
+ console.log('# ping message', idle);
121
292
  this.emit('data', { deviceType: this.deviceType, paired: this.paired, alive: true, ts: Date.now() });
122
293
  }
123
- onPairResponse(d) {
294
+ onResponse(d) {
124
295
  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
- }
296
+ if ((data === null || data === void 0 ? void 0 : data.length) < 1) {
297
+ console.log('Invalid response data', data.toString('hex'));
298
+ return false;
299
+ }
300
+ const type = data.readUInt8(0);
301
+ const message = data.subarray(1);
302
+ if (type === 0x3c) {
303
+ this.onDeviceInformation(message);
136
304
  }
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;
305
+ else {
306
+ const len = data.length;
307
+ if (len === 6 && data.toString() === 'RideOn') {
308
+ this.encrypted = false;
141
309
  this.paired = true;
142
- this.deviceKey = data.subarray(8);
143
310
  this.emitter.emit('paired');
144
311
  try {
145
312
  this.emit('data', { deviceType: this.deviceType, paired: true, ts: Date.now() });
@@ -148,11 +315,26 @@ class BleZwiftPlaySensor extends sensor_1.TBleSensor {
148
315
  this.logEvent({ message: 'error', fn: 'onPairResponse', error: err.message, stack: err.stack });
149
316
  }
150
317
  }
151
- else {
152
- this.logEvent({ message: 'Pairing failed! ', reason: 'unknown message', raw: message });
318
+ else if (len > 8 && Buffer.from(data.subarray(0, 6)).toString() === 'RideOn') {
319
+ const message = Buffer.from(data.subarray(6, 2)).toString('hex');
320
+ if (message === '0900') {
321
+ this.encrypted = true;
322
+ this.paired = true;
323
+ this.deviceKey = data.subarray(8);
324
+ this.emitter.emit('paired');
325
+ try {
326
+ this.emit('data', { deviceType: this.deviceType, paired: true, ts: Date.now() });
327
+ }
328
+ catch (err) {
329
+ this.logEvent({ message: 'error', fn: 'onPairResponse', error: err.message, stack: err.stack });
330
+ }
331
+ }
332
+ else {
333
+ this.logEvent({ message: 'Pairing failed! ', reason: 'unknown message', raw: message });
334
+ }
153
335
  }
336
+ return true;
154
337
  }
155
- return true;
156
338
  }
157
339
  read(characteristic_1) {
158
340
  const _super = Object.create(null, {
@@ -172,6 +354,8 @@ class BleZwiftPlaySensor extends sensor_1.TBleSensor {
172
354
  pair() {
173
355
  return __awaiter(this, void 0, void 0, function* () {
174
356
  var _a, _b;
357
+ if (this.isHubServicePaired)
358
+ return true;
175
359
  let manufacturerData;
176
360
  try {
177
361
  if (this.peripheral.getManufacturerData) {
@@ -197,7 +381,7 @@ class BleZwiftPlaySensor extends sensor_1.TBleSensor {
197
381
  this.logEvent({ message: 'Play protocol pairing info', deviceType: this.deviceType, encrypted: this.encrypted, manufacturerData });
198
382
  let message;
199
383
  if (!this.encrypted) {
200
- message = Buffer.from('RideOn');
384
+ message = this.isFM ? Buffer.concat([Buffer.from('RideOn'), Buffer.from([0x02, 0x01])]) : Buffer.from('RideOn');
201
385
  }
202
386
  else {
203
387
  const { publicKey, privateKey } = this.createKeyPair();
@@ -206,10 +390,12 @@ class BleZwiftPlaySensor extends sensor_1.TBleSensor {
206
390
  message = Buffer.concat([Buffer.from('RideOn'), Buffer.from([0x01, 0x02]), this.publicKey]);
207
391
  }
208
392
  yield this.write((0, utils_1.fullUUID)('00000003-19ca-4651-86e5-fa29dcdd09d1'), message, { withoutResponse: true });
393
+ this.isHubServicePaired = true;
209
394
  return true;
210
395
  }
211
396
  catch (err) {
212
397
  this.logEvent({ message: 'error', fn: 'pair', error: err.message, stack: err.stack });
398
+ this.isHubServicePaired = false;
213
399
  return false;
214
400
  }
215
401
  });
@@ -230,6 +416,10 @@ class BleZwiftPlaySensor extends sensor_1.TBleSensor {
230
416
  this.paired = false;
231
417
  this.encrypted = false;
232
418
  this.deviceKey = null;
419
+ this.isHubServicePaired = false;
420
+ this.isHubServiceSubscribed = false;
421
+ this.isHubServiceActive = false;
422
+ delete this.initHubServicePromise;
233
423
  }
234
424
  getManufacturerData() {
235
425
  const data = this.peripheral.getManufacturerData();
@@ -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) {
@@ -146,6 +154,7 @@ class DirectConnectPeripheral {
146
154
  if (this.subscribed.includes(uuid)) {
147
155
  return true;
148
156
  }
157
+ console.log('# subscribe ', characteristicUUID);
149
158
  const seqNo = this.getNextSeqNo();
150
159
  const message = new messages_1.EnableCharacteristicNotificationsMessage();
151
160
  const request = message.createRequest(seqNo, { characteristicUUID: (0, utils_1.parseUUID)(characteristicUUID), enable: true });
@@ -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: 12, 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
  }
@@ -90,7 +102,8 @@ class SmartTrainerCyclingMode extends power_base_1.default {
90
102
  }
91
103
  }
92
104
  checkSlopeWithAdapterShifting(request, newRequest = {}) {
93
- return this.checkSlopeNoShiftig(request, newRequest);
105
+ this.checkSlopeNoShiftig(request, newRequest);
106
+ newRequest.gearRatio = this.gearRatios[this.gear];
94
107
  }
95
108
  getSlopeDelta() {
96
109
  return this.gearDelta * 0.5;
@@ -170,7 +183,8 @@ class SmartTrainerCyclingMode extends power_base_1.default {
170
183
  case 'Simulated':
171
184
  if (request.gearDelta !== undefined) {
172
185
  if (this.gear === undefined) {
173
- this.gear = 10 + request.gearDelta;
186
+ const initialGear = this.getSetting('startGear');
187
+ this.gear = initialGear + request.gearDelta;
174
188
  }
175
189
  else {
176
190
  this.gear += request.gearDelta;
@@ -192,6 +206,47 @@ class SmartTrainerCyclingMode extends power_base_1.default {
192
206
  }
193
207
  break;
194
208
  case 'Adapter':
209
+ if (request.gearRatio !== undefined) {
210
+ newRequest.gearRatio = request.gearRatio;
211
+ if (this.gear === undefined) {
212
+ const requestedRatio = request.gearRatio;
213
+ let closestIndex = 0;
214
+ let minDiff = Math.abs(this.gearRatios[0] - requestedRatio);
215
+ for (let i = 1; i < this.gearRatios.length; i++) {
216
+ const diff = Math.abs(this.gearRatios[i] - requestedRatio);
217
+ if (diff < minDiff) {
218
+ minDiff = diff;
219
+ closestIndex = i;
220
+ }
221
+ }
222
+ this.gear = closestIndex + 1;
223
+ }
224
+ }
225
+ else if (request.gearDelta !== undefined) {
226
+ if (this.gear === undefined) {
227
+ const initialGear = this.getSetting('startGear');
228
+ this.gear = initialGear + request.gearDelta;
229
+ }
230
+ else {
231
+ this.gear += request.gearDelta;
232
+ }
233
+ if (this.gear < 1) {
234
+ this.gear = 1;
235
+ }
236
+ if (this.gear > this.gearRatios.length) {
237
+ this.gear = this.gearRatios.length;
238
+ }
239
+ delete request.gearDelta;
240
+ newRequest.gearRatio = this.gearRatios[this.gear];
241
+ }
242
+ else {
243
+ if (this.gear === undefined) {
244
+ const initialGear = this.getSetting('startGear');
245
+ this.gear = initialGear + request.gearDelta;
246
+ }
247
+ newRequest.gearRatio = this.gearRatios[this.gear];
248
+ }
249
+ break;
195
250
  case 'Disabled':
196
251
  default:
197
252
  break;
@@ -218,17 +273,22 @@ class SmartTrainerCyclingMode extends power_base_1.default {
218
273
  if (virtshiftMode === 'Disabled') {
219
274
  return 'Disabled';
220
275
  }
221
- if (this.adapter.supportsVirtualShifting()) {
222
- if (virtshiftMode === 'Enabled') {
223
- return 'Adapter';
224
- }
276
+ else if (virtshiftMode === 'Incyclist') {
277
+ return 'Simulated';
278
+ }
279
+ else if (virtshiftMode === 'SmartTrainer') {
280
+ return 'Adapter';
225
281
  }
226
- else {
227
- return virtshiftMode === 'Mixed' ? 'SlopeDelta' : 'Simulated';
282
+ else if (virtshiftMode === 'Mixed') {
283
+ return 'SlopeDelta';
228
284
  }
285
+ else if (virtshiftMode === 'Enabled') {
286
+ return this.adapter.supportsVirtualShifting() ? 'Adapter' : 'Simulated';
287
+ }
288
+ return 'Disabled';
229
289
  }
230
290
  updateData(bikeData, log) {
231
- var _a, _b, _c, _d;
291
+ var _a, _b, _c, _d, _e;
232
292
  const prev = Object.assign({}, this.data);
233
293
  const data = super.updateData(bikeData, log);
234
294
  const mode = this.getVirtualShiftMode();
@@ -241,14 +301,14 @@ class SmartTrainerCyclingMode extends power_base_1.default {
241
301
  this.tsStart = Date.now();
242
302
  }
243
303
  if (this.gear === undefined && this.tsStart && data.power > 0 && (Date.now() - this.tsStart > 3000)) {
244
- this.setInitialGear(data);
304
+ this.gear = (_a = this.getSetting('startGear')) !== null && _a !== void 0 ? _a : 0;
245
305
  data.gearStr = this.getGearString();
246
306
  }
247
307
  else if (this.gear !== undefined) {
248
308
  if (prev.power !== data.power || prev.pedalRpm !== data.pedalRpm) {
249
309
  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);
310
+ const m = (_c = (_b = this.adapter) === null || _b === void 0 ? void 0 : _b.getWeight()) !== null && _c !== void 0 ? _c : 85;
311
+ 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
312
  this.verifySimPower();
253
313
  }
254
314
  }
@@ -279,25 +339,22 @@ class SmartTrainerCyclingMode extends power_base_1.default {
279
339
  }
280
340
  return newRequest;
281
341
  }
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
342
  getGearString() {
343
+ var _a, _b, _c, _d, _e;
293
344
  const mode = this.getVirtualShiftMode();
294
345
  if (mode === "Disabled")
295
346
  return undefined;
296
- if (mode === 'Simulated' && (this.gear === undefined || this.gear === null))
297
- return '';
347
+ if (mode === 'Simulated') {
348
+ this.gear = (_b = (_a = this.gear) !== null && _a !== void 0 ? _a : this.getSetting('startGear')) !== null && _b !== void 0 ? _b : 0;
349
+ return this.gear.toString();
350
+ }
298
351
  if (mode === "SlopeDelta")
299
352
  return this.gearDelta > 0 ? `+${this.gearDelta}` : `${this.gearDelta}`;
300
- return this.gear.toString();
353
+ if (mode === 'Adapter') {
354
+ this.gear = (_d = (_c = this.gear) !== null && _c !== void 0 ? _c : this.getSetting('startGear')) !== null && _d !== void 0 ? _d : 0;
355
+ return this.gear.toString();
356
+ }
357
+ return (_e = this.gear) === null || _e === void 0 ? void 0 : _e.toString();
301
358
  }
302
359
  }
303
360
  SmartTrainerCyclingMode.config = {