incyclist-devices 2.3.28 → 2.3.29
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/antv2/types.d.ts +1 -1
- package/lib/base/adpater.d.ts +1 -0
- package/lib/base/adpater.js +21 -11
- package/lib/ble/base/adapter.js +2 -1
- package/lib/ble/base/interface.js +11 -4
- package/lib/ble/base/peripheral.d.ts +3 -2
- package/lib/ble/base/peripheral.js +62 -38
- package/lib/ble/base/sensor.d.ts +2 -0
- package/lib/ble/base/sensor.js +16 -4
- package/lib/ble/fm/adapter.js +8 -7
- package/lib/ble/index.js +2 -0
- package/lib/ble/types.d.ts +4 -2
- package/lib/ble/zwift/click/adapter.d.ts +19 -0
- package/lib/ble/zwift/click/adapter.js +74 -0
- package/lib/ble/zwift/click/index.d.ts +2 -0
- package/lib/ble/zwift/click/index.js +18 -0
- package/lib/ble/zwift/click/sensor.d.ts +40 -0
- package/lib/ble/zwift/click/sensor.js +221 -0
- package/lib/ble/zwift/play/adapter.d.ts +19 -0
- package/lib/ble/zwift/play/adapter.js +76 -0
- package/lib/ble/zwift/play/index.d.ts +2 -0
- package/lib/ble/zwift/play/index.js +18 -0
- package/lib/ble/zwift/play/sensor.d.ts +43 -0
- package/lib/ble/zwift/play/sensor.js +246 -0
- package/lib/modes/power-base.js +17 -3
- package/lib/types/capabilities.d.ts +2 -1
- package/lib/types/capabilities.js +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.BleZwiftClickSensor = void 0;
|
|
13
|
+
const sensor_1 = require("../../base/sensor");
|
|
14
|
+
const utils_1 = require("../../utils");
|
|
15
|
+
const crypto_1 = require("crypto");
|
|
16
|
+
const stream_1 = require("stream");
|
|
17
|
+
class BleZwiftClickSensor extends sensor_1.TBleSensor {
|
|
18
|
+
constructor(peripheral, props) {
|
|
19
|
+
super(peripheral, props);
|
|
20
|
+
this.emitter = new stream_1.EventEmitter();
|
|
21
|
+
this.setInitialState();
|
|
22
|
+
}
|
|
23
|
+
reconnectSensor() {
|
|
24
|
+
const _super = Object.create(null, {
|
|
25
|
+
reconnectSensor: { get: () => super.reconnectSensor }
|
|
26
|
+
});
|
|
27
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
28
|
+
this.setInitialState();
|
|
29
|
+
let reconnected = yield _super.reconnectSensor.call(this);
|
|
30
|
+
this.pair();
|
|
31
|
+
return reconnected;
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
stopSensor() {
|
|
35
|
+
this.emitter.removeAllListeners();
|
|
36
|
+
this.setInitialState();
|
|
37
|
+
this.removeAllListeners('up');
|
|
38
|
+
this.removeAllListeners('down');
|
|
39
|
+
return super.stopSensor();
|
|
40
|
+
}
|
|
41
|
+
getRequiredCharacteristics() {
|
|
42
|
+
return ['00000002-19ca-4651-86e5-fa29dcdd09d1', '00000004-19ca-4651-86e5-fa29dcdd09d1'];
|
|
43
|
+
}
|
|
44
|
+
onData(characteristic, data, isNotify) {
|
|
45
|
+
const uuid = (0, utils_1.beautifyUUID)(characteristic).toLowerCase();
|
|
46
|
+
if (uuid === '00000002-19ca-4651-86e5-fa29dcdd09d1') {
|
|
47
|
+
this.onPlayMeasurement(data);
|
|
48
|
+
}
|
|
49
|
+
else if (uuid === '00000004-19ca-4651-86e5-fa29dcdd09d1') {
|
|
50
|
+
this.onPairResponse(data);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
console.log('data received ', isNotify ? 'N' : 'I', uuid, data.toString('hex'));
|
|
54
|
+
}
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
onPlayMeasurement(data) {
|
|
58
|
+
if ((data === null || data === void 0 ? void 0 : data.length) < 1) {
|
|
59
|
+
console.log('Invalid click measurement data', data.toString('hex'));
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
const type = data.readUInt8(0);
|
|
63
|
+
const message = data.subarray(1);
|
|
64
|
+
if (type === 0x37) {
|
|
65
|
+
this.onButtonMessage(message);
|
|
66
|
+
}
|
|
67
|
+
else if (type === 0x19) {
|
|
68
|
+
this.onPingMessage(message);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
console.log('Unknown click measurement type', type, message.toString('hex'));
|
|
72
|
+
this.emit('data', { raw: data.toString('hex') });
|
|
73
|
+
}
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
onButtonMessage(message) {
|
|
77
|
+
var _a, _b;
|
|
78
|
+
if (message.readUInt8(0) === 0x8 && message.length === 4) {
|
|
79
|
+
const value = message.subarray(1);
|
|
80
|
+
const messageStr = value.toString('hex');
|
|
81
|
+
if (messageStr === this.prevClickMessage) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
switch (messageStr) {
|
|
85
|
+
case '001001':
|
|
86
|
+
this.upState = { pressed: true, timestamp: Date.now() };
|
|
87
|
+
break;
|
|
88
|
+
case '011001':
|
|
89
|
+
if ((_a = this.upState) === null || _a === void 0 ? void 0 : _a.pressed) {
|
|
90
|
+
const prev = Object.assign({}, this.upState);
|
|
91
|
+
this.upState = { pressed: false, timestamp: Date.now() };
|
|
92
|
+
this.emit('up', this.upState.timestamp - prev.timestamp);
|
|
93
|
+
}
|
|
94
|
+
else if ((_b = this.downState) === null || _b === void 0 ? void 0 : _b.pressed) {
|
|
95
|
+
const prev = Object.assign({}, this.downState);
|
|
96
|
+
this.downState = { pressed: false, timestamp: Date.now() };
|
|
97
|
+
this.emit('down', this.downState.timestamp - prev.timestamp);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
this.upState = { pressed: false, timestamp: Date.now() };
|
|
101
|
+
this.downState = { pressed: false, timestamp: Date.now() };
|
|
102
|
+
}
|
|
103
|
+
break;
|
|
104
|
+
case '011000':
|
|
105
|
+
this.downState = { pressed: true, timestamp: Date.now() };
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
this.prevClickMessage = messageStr;
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
console.log('Click measurement received', message.toString('hex'), message.readUInt8(0), message.length);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
onPingMessage(message) {
|
|
115
|
+
this.emit('data', { deviceType: this.deviceType, paired: this.paired, alive: true, ts: Date.now() });
|
|
116
|
+
}
|
|
117
|
+
onPairResponse(data) {
|
|
118
|
+
const len = data.length;
|
|
119
|
+
if (len === 6 && data.toString() === 'RideOn') {
|
|
120
|
+
this.encrypted = false;
|
|
121
|
+
this.paired = true;
|
|
122
|
+
this.emitter.emit('paired');
|
|
123
|
+
try {
|
|
124
|
+
this.emit('data', { deviceType: this.deviceType, paired: true, ts: Date.now() });
|
|
125
|
+
}
|
|
126
|
+
catch (e) {
|
|
127
|
+
console.error('Error emitting data:', e);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
else if (len > 8 && data.subarray(0, 6).toString() === 'RideOn') {
|
|
131
|
+
const message = data.subarray(6, 2).toString('hex');
|
|
132
|
+
if (message === '0900') {
|
|
133
|
+
this.encrypted = true;
|
|
134
|
+
this.paired = true;
|
|
135
|
+
this.deviceKey = data.subarray(8);
|
|
136
|
+
this.emitter.emit('paired');
|
|
137
|
+
try {
|
|
138
|
+
this.emit('data', { deviceType: this.deviceType, paired: true, ts: Date.now() });
|
|
139
|
+
}
|
|
140
|
+
catch (e) {
|
|
141
|
+
console.error('Error emitting data:', e);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
console.log('Pairing failed! ', { reason: 'unknown message', message });
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
read(characteristic_1) {
|
|
151
|
+
const _super = Object.create(null, {
|
|
152
|
+
read: { get: () => super.read }
|
|
153
|
+
});
|
|
154
|
+
return __awaiter(this, arguments, void 0, function* (characteristic, ignoreErrors = false) {
|
|
155
|
+
try {
|
|
156
|
+
return yield _super.read.call(this, characteristic);
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
if (!ignoreErrors)
|
|
160
|
+
throw error;
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
pair() {
|
|
166
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
167
|
+
var _a, _b;
|
|
168
|
+
try {
|
|
169
|
+
if (this.peripheral.getManufacturerData) {
|
|
170
|
+
const manufacturerData = this.peripheral.getManufacturerData();
|
|
171
|
+
if (manufacturerData === null || manufacturerData === void 0 ? void 0 : manufacturerData.startsWith('4a09')) {
|
|
172
|
+
const typeVal = Number('0x' + manufacturerData.substring(2, 4));
|
|
173
|
+
if (typeVal === 9) {
|
|
174
|
+
this.deviceType = 'click';
|
|
175
|
+
}
|
|
176
|
+
else if (typeVal === 2) {
|
|
177
|
+
this.deviceType = 'right';
|
|
178
|
+
}
|
|
179
|
+
else if (typeVal === 3) {
|
|
180
|
+
this.deviceType = 'left';
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
let message;
|
|
185
|
+
if (this.deviceType === 'click') {
|
|
186
|
+
message = Buffer.from('RideOn');
|
|
187
|
+
this.encrypted = false;
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
const { publicKey, privateKey } = (0, crypto_1.generateKeyPairSync)('ec', {
|
|
191
|
+
namedCurve: 'prime256v1',
|
|
192
|
+
publicKeyEncoding: { type: 'spki', format: 'der' },
|
|
193
|
+
privateKeyEncoding: { type: 'pkcs8', format: 'der' }
|
|
194
|
+
});
|
|
195
|
+
this.privateKey = (_a = this.privateKey) !== null && _a !== void 0 ? _a : Buffer.from(privateKey);
|
|
196
|
+
this.publicKey = (_b = this.publicKey) !== null && _b !== void 0 ? _b : Buffer.from(publicKey);
|
|
197
|
+
message = Buffer.concat([Buffer.from('RideOn'), Buffer.from([0x01, 0x02]), this.publicKey]);
|
|
198
|
+
}
|
|
199
|
+
yield this.write((0, utils_1.fullUUID)('00000003-19ca-4651-86e5-fa29dcdd09d1'), message, { withoutResponse: true });
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
catch (error) {
|
|
203
|
+
console.error('Error pairing:', error);
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
reset() {
|
|
209
|
+
}
|
|
210
|
+
setInitialState() {
|
|
211
|
+
this.paired = false;
|
|
212
|
+
this.encrypted = false;
|
|
213
|
+
this.deviceKey = null;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
exports.BleZwiftClickSensor = BleZwiftClickSensor;
|
|
217
|
+
BleZwiftClickSensor.profile = 'Controller';
|
|
218
|
+
BleZwiftClickSensor.protocol = 'zwift-click';
|
|
219
|
+
BleZwiftClickSensor.services = ['0000000119ca465186e5fa29dcdd09d1'];
|
|
220
|
+
BleZwiftClickSensor.characteristics = [];
|
|
221
|
+
BleZwiftClickSensor.detectionPriority = 1;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { BleDeviceData } from '../../base/types';
|
|
2
|
+
import { LegacyProfile } from '../../../antv2/types';
|
|
3
|
+
import BleAdapter from '../../base/adapter';
|
|
4
|
+
import { BleZwiftPlaySensor } from './sensor';
|
|
5
|
+
import { DeviceProperties, IAdapter, IncyclistAdapterData, IncyclistCapability } from '../../../types';
|
|
6
|
+
import { BleDeviceSettings, BleStartProperties, IBlePeripheral } from '../../types';
|
|
7
|
+
export declare class ZwiftPlayAdapter extends BleAdapter<BleDeviceData, BleZwiftPlaySensor> {
|
|
8
|
+
protected static INCYCLIST_PROFILE_NAME: LegacyProfile;
|
|
9
|
+
protected static CAPABILITIES: IncyclistCapability[];
|
|
10
|
+
constructor(settings: BleDeviceSettings, props?: DeviceProperties);
|
|
11
|
+
protected checkCapabilities(): Promise<void>;
|
|
12
|
+
start(startProps?: BleStartProperties): Promise<boolean>;
|
|
13
|
+
startSensor(): Promise<boolean>;
|
|
14
|
+
isSame(adapter: IAdapter): boolean;
|
|
15
|
+
updateSensor(peripheral: IBlePeripheral): void;
|
|
16
|
+
getProfile(): LegacyProfile;
|
|
17
|
+
getDisplayName(): string;
|
|
18
|
+
mapData(deviceData: BleDeviceData): IncyclistAdapterData;
|
|
19
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.ZwiftPlayAdapter = void 0;
|
|
16
|
+
const gd_eventlog_1 = require("gd-eventlog");
|
|
17
|
+
const adapter_1 = __importDefault(require("../../base/adapter"));
|
|
18
|
+
const sensor_1 = require("./sensor");
|
|
19
|
+
const types_1 = require("../../../types");
|
|
20
|
+
class ZwiftPlayAdapter extends adapter_1.default {
|
|
21
|
+
constructor(settings, props) {
|
|
22
|
+
super(settings, props);
|
|
23
|
+
this.logger = new gd_eventlog_1.EventLogger('ZwiftPlay');
|
|
24
|
+
this.device = new sensor_1.BleZwiftPlaySensor(this.getPeripheral(), { logger: this.logger });
|
|
25
|
+
this.capabilities = ZwiftPlayAdapter.CAPABILITIES;
|
|
26
|
+
}
|
|
27
|
+
checkCapabilities() {
|
|
28
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
29
|
+
return;
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
start(startProps) {
|
|
33
|
+
return super.start(startProps);
|
|
34
|
+
}
|
|
35
|
+
startSensor() {
|
|
36
|
+
const _super = Object.create(null, {
|
|
37
|
+
startSensor: { get: () => super.startSensor }
|
|
38
|
+
});
|
|
39
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
40
|
+
try {
|
|
41
|
+
let connected = yield _super.startSensor.call(this);
|
|
42
|
+
if (connected) {
|
|
43
|
+
const sensor = this.getSensor();
|
|
44
|
+
sensor.on('key-pressed', (event) => {
|
|
45
|
+
this.emit('key-pressed', this.getSettings(), event);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
return connected;
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
this.logEvent({ message: 'error', fn: 'startSensor', error: err.message, stack: err.stack });
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
isSame(adapter) {
|
|
57
|
+
if (!(adapter instanceof ZwiftPlayAdapter))
|
|
58
|
+
return false;
|
|
59
|
+
return this.isEqual(adapter.settings);
|
|
60
|
+
}
|
|
61
|
+
updateSensor(peripheral) {
|
|
62
|
+
this.device = new sensor_1.BleZwiftPlaySensor(peripheral, { logger: this.logger });
|
|
63
|
+
}
|
|
64
|
+
getProfile() {
|
|
65
|
+
return ZwiftPlayAdapter.INCYCLIST_PROFILE_NAME;
|
|
66
|
+
}
|
|
67
|
+
getDisplayName() {
|
|
68
|
+
return this.getName();
|
|
69
|
+
}
|
|
70
|
+
mapData(deviceData) {
|
|
71
|
+
return {};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
exports.ZwiftPlayAdapter = ZwiftPlayAdapter;
|
|
75
|
+
ZwiftPlayAdapter.INCYCLIST_PROFILE_NAME = 'Controller';
|
|
76
|
+
ZwiftPlayAdapter.CAPABILITIES = [types_1.IncyclistCapability.AppControl];
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./sensor"), exports);
|
|
18
|
+
__exportStar(require("./adapter"), exports);
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { LegacyProfile } from "incyclist-devices/lib/antv2/types";
|
|
2
|
+
import { TBleSensor } from "../../base/sensor";
|
|
3
|
+
import { BleProtocol } from "../../types";
|
|
4
|
+
import { EventEmitter } from "events";
|
|
5
|
+
type ButtonState = {
|
|
6
|
+
pressed: boolean;
|
|
7
|
+
timestamp: number;
|
|
8
|
+
};
|
|
9
|
+
type DeviceType = 'left' | 'right' | 'click';
|
|
10
|
+
export declare class BleZwiftPlaySensor extends TBleSensor {
|
|
11
|
+
static readonly profile: LegacyProfile;
|
|
12
|
+
static readonly protocol: BleProtocol;
|
|
13
|
+
static readonly services: string[];
|
|
14
|
+
static readonly characteristics: any[];
|
|
15
|
+
static readonly detectionPriority = 1;
|
|
16
|
+
protected emitter: EventEmitter;
|
|
17
|
+
protected paired: boolean;
|
|
18
|
+
protected encrypted: boolean;
|
|
19
|
+
protected deviceKey: Buffer;
|
|
20
|
+
protected prevClickMessage: string;
|
|
21
|
+
protected upState: ButtonState;
|
|
22
|
+
protected downState: ButtonState;
|
|
23
|
+
protected deviceType: DeviceType;
|
|
24
|
+
protected publicKey: Buffer;
|
|
25
|
+
protected privateKey: Buffer;
|
|
26
|
+
constructor(peripheral: any, props?: any);
|
|
27
|
+
reconnectSensor(): Promise<void>;
|
|
28
|
+
stopSensor(): Promise<boolean>;
|
|
29
|
+
protected getRequiredCharacteristics(): Array<string>;
|
|
30
|
+
onData(characteristic: string, data: Buffer, isNotify?: boolean): boolean;
|
|
31
|
+
onPlayMeasurement(d: Buffer): boolean;
|
|
32
|
+
onButtonMessage(d: Buffer): void;
|
|
33
|
+
onPingMessage(d: Buffer): void;
|
|
34
|
+
onPairResponse(d: Buffer): boolean;
|
|
35
|
+
read(characteristic: string, ignoreErrors?: boolean): Promise<Buffer | null>;
|
|
36
|
+
pair(): Promise<boolean>;
|
|
37
|
+
reset(): void;
|
|
38
|
+
protected encrpytedSupported(): boolean;
|
|
39
|
+
protected createKeyPair(): import("crypto").KeyPairSyncResult<Buffer<ArrayBufferLike>, Buffer<ArrayBufferLike>>;
|
|
40
|
+
protected setInitialState(): void;
|
|
41
|
+
protected getManufacturerData(): string;
|
|
42
|
+
}
|
|
43
|
+
export {};
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.BleZwiftPlaySensor = void 0;
|
|
13
|
+
const sensor_1 = require("../../base/sensor");
|
|
14
|
+
const utils_1 = require("../../utils");
|
|
15
|
+
const crypto_1 = require("crypto");
|
|
16
|
+
const events_1 = require("events");
|
|
17
|
+
class BleZwiftPlaySensor extends sensor_1.TBleSensor {
|
|
18
|
+
constructor(peripheral, props) {
|
|
19
|
+
super(peripheral, props);
|
|
20
|
+
this.emitter = new events_1.EventEmitter();
|
|
21
|
+
this.setInitialState();
|
|
22
|
+
}
|
|
23
|
+
reconnectSensor() {
|
|
24
|
+
const _super = Object.create(null, {
|
|
25
|
+
reconnectSensor: { get: () => super.reconnectSensor }
|
|
26
|
+
});
|
|
27
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
28
|
+
this.setInitialState();
|
|
29
|
+
let reconnected = yield _super.reconnectSensor.call(this);
|
|
30
|
+
this.pair();
|
|
31
|
+
return reconnected;
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
stopSensor() {
|
|
35
|
+
this.emitter.removeAllListeners();
|
|
36
|
+
this.setInitialState();
|
|
37
|
+
this.removeAllListeners('key-pressed');
|
|
38
|
+
return super.stopSensor();
|
|
39
|
+
}
|
|
40
|
+
getRequiredCharacteristics() {
|
|
41
|
+
return ['00000002-19ca-4651-86e5-fa29dcdd09d1', '00000004-19ca-4651-86e5-fa29dcdd09d1'];
|
|
42
|
+
}
|
|
43
|
+
onData(characteristic, data, isNotify) {
|
|
44
|
+
const uuid = (0, utils_1.beautifyUUID)(characteristic).toLowerCase();
|
|
45
|
+
if (uuid === '00000002-19ca-4651-86e5-fa29dcdd09d1') {
|
|
46
|
+
this.onPlayMeasurement(data);
|
|
47
|
+
}
|
|
48
|
+
else if (uuid === '00000004-19ca-4651-86e5-fa29dcdd09d1') {
|
|
49
|
+
this.onPairResponse(data);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
console.log('data received ', isNotify ? 'N' : 'I', uuid, data.toString('hex'));
|
|
53
|
+
}
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
onPlayMeasurement(d) {
|
|
57
|
+
const data = Buffer.from(d);
|
|
58
|
+
if ((data === null || data === void 0 ? void 0 : data.length) < 1) {
|
|
59
|
+
console.log('Invalid click measurement data', data.toString('hex'));
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
const type = data.readUInt8(0);
|
|
63
|
+
const message = data.subarray(1);
|
|
64
|
+
if (type === 0x37) {
|
|
65
|
+
this.onButtonMessage(message);
|
|
66
|
+
}
|
|
67
|
+
else if (type === 0x19) {
|
|
68
|
+
this.onPingMessage(message);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
console.log('Unknown click measurement type', type, message.toString('hex'));
|
|
72
|
+
this.emit('data', { raw: data.toString('hex') });
|
|
73
|
+
}
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
onButtonMessage(d) {
|
|
77
|
+
var _a, _b;
|
|
78
|
+
const message = Buffer.from(d);
|
|
79
|
+
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;
|
|
85
|
+
}
|
|
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;
|
|
109
|
+
}
|
|
110
|
+
this.prevClickMessage = messageStr;
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
this.logEvent({ message: 'Click measurement received', raw: message.toString('hex') });
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
this.logEvent({ message: 'error', fn: 'onButtonMessage', error: err.message, stack: err.stack });
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
onPingMessage(d) {
|
|
121
|
+
this.emit('data', { deviceType: this.deviceType, paired: this.paired, alive: true, ts: Date.now() });
|
|
122
|
+
}
|
|
123
|
+
onPairResponse(d) {
|
|
124
|
+
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
|
+
}
|
|
136
|
+
}
|
|
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;
|
|
141
|
+
this.paired = true;
|
|
142
|
+
this.deviceKey = data.subarray(8);
|
|
143
|
+
this.emitter.emit('paired');
|
|
144
|
+
try {
|
|
145
|
+
this.emit('data', { deviceType: this.deviceType, paired: true, ts: Date.now() });
|
|
146
|
+
}
|
|
147
|
+
catch (err) {
|
|
148
|
+
this.logEvent({ message: 'error', fn: 'onPairResponse', error: err.message, stack: err.stack });
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
this.logEvent({ message: 'Pairing failed! ', reason: 'unknown message', raw: message });
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
read(characteristic_1) {
|
|
158
|
+
const _super = Object.create(null, {
|
|
159
|
+
read: { get: () => super.read }
|
|
160
|
+
});
|
|
161
|
+
return __awaiter(this, arguments, void 0, function* (characteristic, ignoreErrors = false) {
|
|
162
|
+
try {
|
|
163
|
+
return yield _super.read.call(this, characteristic);
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
if (!ignoreErrors)
|
|
167
|
+
throw error;
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
pair() {
|
|
173
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
174
|
+
var _a, _b;
|
|
175
|
+
try {
|
|
176
|
+
if (this.peripheral.getManufacturerData) {
|
|
177
|
+
const manufacturerData = this.getManufacturerData();
|
|
178
|
+
if (manufacturerData === null || manufacturerData === void 0 ? void 0 : manufacturerData.startsWith('4a09')) {
|
|
179
|
+
const typeVal = Number('0x' + manufacturerData.substring(2, 4));
|
|
180
|
+
if (typeVal === 9) {
|
|
181
|
+
this.deviceType = 'click';
|
|
182
|
+
}
|
|
183
|
+
else if (typeVal === 2) {
|
|
184
|
+
this.deviceType = 'right';
|
|
185
|
+
}
|
|
186
|
+
else if (typeVal === 3) {
|
|
187
|
+
this.deviceType = 'left';
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
else if (!this.encrpytedSupported()) {
|
|
192
|
+
this.deviceType = 'click';
|
|
193
|
+
}
|
|
194
|
+
let message;
|
|
195
|
+
if (this.deviceType === 'click') {
|
|
196
|
+
message = Buffer.from('RideOn');
|
|
197
|
+
this.encrypted = false;
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
const { publicKey, privateKey } = this.createKeyPair();
|
|
201
|
+
this.privateKey = (_a = this.privateKey) !== null && _a !== void 0 ? _a : Buffer.from(privateKey);
|
|
202
|
+
this.publicKey = (_b = this.publicKey) !== null && _b !== void 0 ? _b : Buffer.from(publicKey);
|
|
203
|
+
message = Buffer.concat([Buffer.from('RideOn'), Buffer.from([0x01, 0x02]), this.publicKey]);
|
|
204
|
+
}
|
|
205
|
+
yield this.write((0, utils_1.fullUUID)('00000003-19ca-4651-86e5-fa29dcdd09d1'), message, { withoutResponse: true });
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
catch (err) {
|
|
209
|
+
this.logEvent({ message: 'error', fn: 'pair', error: err.message, stack: err.stack });
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
reset() {
|
|
215
|
+
}
|
|
216
|
+
encrpytedSupported() {
|
|
217
|
+
return crypto_1.generateKeyPairSync !== undefined && typeof (crypto_1.generateKeyPairSync) === 'function';
|
|
218
|
+
}
|
|
219
|
+
createKeyPair() {
|
|
220
|
+
return (0, crypto_1.generateKeyPairSync)('ec', {
|
|
221
|
+
namedCurve: 'prime256v1',
|
|
222
|
+
publicKeyEncoding: { type: 'spki', format: 'der' },
|
|
223
|
+
privateKeyEncoding: { type: 'pkcs8', format: 'der' }
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
setInitialState() {
|
|
227
|
+
this.paired = false;
|
|
228
|
+
this.encrypted = false;
|
|
229
|
+
this.deviceKey = null;
|
|
230
|
+
}
|
|
231
|
+
getManufacturerData() {
|
|
232
|
+
const data = this.peripheral.getManufacturerData();
|
|
233
|
+
if (typeof data === 'string')
|
|
234
|
+
return data;
|
|
235
|
+
if (Buffer.isBuffer(data)) {
|
|
236
|
+
return data.toString('hex');
|
|
237
|
+
}
|
|
238
|
+
return undefined;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
exports.BleZwiftPlaySensor = BleZwiftPlaySensor;
|
|
242
|
+
BleZwiftPlaySensor.profile = 'Controller';
|
|
243
|
+
BleZwiftPlaySensor.protocol = 'zwift-play';
|
|
244
|
+
BleZwiftPlaySensor.services = ['0000000119ca465186e5fa29dcdd09d1'];
|
|
245
|
+
BleZwiftPlaySensor.characteristics = [];
|
|
246
|
+
BleZwiftPlaySensor.detectionPriority = 1;
|
package/lib/modes/power-base.js
CHANGED
|
@@ -54,10 +54,12 @@ class PowerBasedCyclingModeBase extends base_1.CyclingModeBase {
|
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
checkRefresh(request, newRequest) {
|
|
57
|
+
var _a;
|
|
57
58
|
if (request.refresh && request.targetPower === undefined) {
|
|
58
59
|
delete request.refresh;
|
|
59
|
-
if (this.prevRequest)
|
|
60
|
-
newRequest.targetPower = this.prevRequest.targetPower;
|
|
60
|
+
if (this.prevRequest) {
|
|
61
|
+
newRequest.targetPower = (_a = this.prevRequest.targetPower) !== null && _a !== void 0 ? _a : this.data.power;
|
|
62
|
+
}
|
|
61
63
|
}
|
|
62
64
|
}
|
|
63
65
|
checkTargetPowerSet(request, newRequest) {
|
|
@@ -73,9 +75,19 @@ class PowerBasedCyclingModeBase extends base_1.CyclingModeBase {
|
|
|
73
75
|
}
|
|
74
76
|
}
|
|
75
77
|
checkSlope(request) {
|
|
78
|
+
var _a;
|
|
76
79
|
if (request.slope !== undefined) {
|
|
77
80
|
this.data.slope = request.slope;
|
|
78
81
|
delete request.slope;
|
|
82
|
+
if (request.targetPower === undefined && request.minPower === undefined && request.maxPower === undefined) {
|
|
83
|
+
if (this.prevRequest) {
|
|
84
|
+
this.prevRequest.targetPower = (_a = this.prevRequest.targetPower) !== null && _a !== void 0 ? _a : (this.data.power || undefined);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
this.prevRequest = { targetPower: this.data.power || undefined };
|
|
88
|
+
}
|
|
89
|
+
request.refresh = this.prevRequest.targetPower !== undefined;
|
|
90
|
+
}
|
|
79
91
|
}
|
|
80
92
|
}
|
|
81
93
|
checkEmptyRequest(newRequest) {
|
|
@@ -213,8 +225,10 @@ class PowerBasedCyclingModeBase extends base_1.CyclingModeBase {
|
|
|
213
225
|
const request = Object.assign({}, incoming);
|
|
214
226
|
try {
|
|
215
227
|
const req = this.checkForResetOrEmpty(request);
|
|
216
|
-
if (req)
|
|
228
|
+
if (req) {
|
|
229
|
+
delete req.refresh;
|
|
217
230
|
return req;
|
|
231
|
+
}
|
|
218
232
|
this.checkSlope(request);
|
|
219
233
|
this.checkForTempPowerAdjustments(request);
|
|
220
234
|
this.checkTargetPowerSet(request, newRequest);
|