incyclist-devices 3.0.24 → 3.0.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/cjs/ble/base/adapter.js +17 -8
- package/lib/cjs/ble/fm/adapter.js +43 -1
- package/lib/cjs/ble/fm/sensor.js +0 -3
- package/lib/cjs/direct-connect/base/interface.js +57 -20
- package/lib/esm/ble/base/adapter.js +17 -8
- package/lib/esm/ble/fm/adapter.js +43 -1
- package/lib/esm/ble/fm/sensor.js +0 -3
- package/lib/esm/direct-connect/base/interface.js +57 -20
- package/lib/types/ble/base/adapter.d.ts +1 -0
- package/lib/types/ble/fm/adapter.d.ts +3 -0
- package/lib/types/direct-connect/base/interface.d.ts +6 -1
- package/package.json +1 -1
|
@@ -55,7 +55,7 @@ class BleAdapter extends adpater_js_1.default {
|
|
|
55
55
|
const info = peripheral.getInfo();
|
|
56
56
|
const settings = { ...this.settings };
|
|
57
57
|
settings.id = settings.id ?? info.id;
|
|
58
|
-
settings.address =
|
|
58
|
+
settings.address = info.address ?? settings.address;
|
|
59
59
|
settings.name = settings.name ?? info.name;
|
|
60
60
|
this.settings = settings;
|
|
61
61
|
}
|
|
@@ -77,11 +77,13 @@ class BleAdapter extends adpater_js_1.default {
|
|
|
77
77
|
if (as.profile || settings.profile) {
|
|
78
78
|
return (as.protocol === settings.protocol && as.profile === settings.profile && as.name === settings.name);
|
|
79
79
|
}
|
|
80
|
-
|
|
81
|
-
return
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
80
|
+
if (as.protocol !== settings.protocol)
|
|
81
|
+
return false;
|
|
82
|
+
if (as.id && settings.id)
|
|
83
|
+
return as.id === settings.id;
|
|
84
|
+
if (as.address && settings.address)
|
|
85
|
+
return as.address === settings.address;
|
|
86
|
+
return !!(as.name && settings.name && as.name === settings.name);
|
|
85
87
|
}
|
|
86
88
|
isSame(adapter) {
|
|
87
89
|
return this.isEqual(adapter.getSettings());
|
|
@@ -251,6 +253,8 @@ class BleAdapter extends adpater_js_1.default {
|
|
|
251
253
|
}
|
|
252
254
|
async initControl(_props) {
|
|
253
255
|
}
|
|
256
|
+
async initControlBestEffort(_props) {
|
|
257
|
+
}
|
|
254
258
|
getStartLogProps(props) {
|
|
255
259
|
const capabilities = this.props?.capabilities;
|
|
256
260
|
const { user, userWeight, bikeWeight, timeout, wheelDiameter, restart, scanOnly } = props ?? {};
|
|
@@ -286,11 +290,16 @@ class BleAdapter extends adpater_js_1.default {
|
|
|
286
290
|
this.stopped = true;
|
|
287
291
|
return false;
|
|
288
292
|
}
|
|
289
|
-
await this.waitForInitialData(timeout);
|
|
290
293
|
await this.checkCapabilities();
|
|
291
294
|
const skipControl = this.props.capabilities && !this.props.capabilities.includes(index_js_1.IncyclistCapability.Control);
|
|
292
|
-
|
|
295
|
+
const isControllable = this.hasCapability(index_js_1.IncyclistCapability.Control) && !skipControl;
|
|
296
|
+
if (isControllable) {
|
|
293
297
|
await this.initControl(startProps);
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
await this.initControlBestEffort(startProps);
|
|
301
|
+
}
|
|
302
|
+
await this.waitForInitialData(timeout);
|
|
294
303
|
this.stopped = false;
|
|
295
304
|
this.started = true;
|
|
296
305
|
if (wasPaused)
|
|
@@ -12,6 +12,7 @@ const adapter_js_1 = __importDefault(require("../base/adapter.js"));
|
|
|
12
12
|
const consts_js_1 = require("./consts.js");
|
|
13
13
|
const index_js_1 = require("../../types/index.js");
|
|
14
14
|
const utils_js_1 = require("../../utils/utils.js");
|
|
15
|
+
const task_js_1 = require("../../utils/task.js");
|
|
15
16
|
const index_js_2 = require("../zwift/play/index.js");
|
|
16
17
|
const index_js_3 = require("../../features/index.js");
|
|
17
18
|
const fm_resistance_js_1 = __importDefault(require("../../modes/fm-resistance.js"));
|
|
@@ -21,6 +22,7 @@ class BleFmAdapter extends adapter_js_1.default {
|
|
|
21
22
|
distanceInternal = 0;
|
|
22
23
|
connectPromise;
|
|
23
24
|
requestControlRetryDelay = 1000;
|
|
25
|
+
requestControlTimeout = 10000;
|
|
24
26
|
promiseSendUpdate;
|
|
25
27
|
zwiftPlay;
|
|
26
28
|
isHubInitialized = false;
|
|
@@ -168,8 +170,36 @@ class BleFmAdapter extends adapter_js_1.default {
|
|
|
168
170
|
if (!this.hasCapability(index_js_1.IncyclistCapability.Control))
|
|
169
171
|
return;
|
|
170
172
|
await this.establishControl();
|
|
173
|
+
await this.attemptStartRequest();
|
|
171
174
|
await this.sendInitialRequest();
|
|
172
175
|
}
|
|
176
|
+
async initControlBestEffort(_startProps) {
|
|
177
|
+
if (!this.isStarting())
|
|
178
|
+
return;
|
|
179
|
+
this.setConstants();
|
|
180
|
+
try {
|
|
181
|
+
const hasControl = await this.getSensor().requestControl();
|
|
182
|
+
if (hasControl) {
|
|
183
|
+
this.logEvent({ message: 'control granted (downgraded device)', device: this.getName(), interface: this.getInterface() });
|
|
184
|
+
await this.attemptStartRequest();
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
this.logEvent({ message: 'could not establish control (downgraded device, non-fatal)', device: this.getName(), interface: this.getInterface() });
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
catch (err) {
|
|
191
|
+
this.logEvent({ message: 'could not establish control (downgraded device, non-fatal)', device: this.getName(), interface: this.getInterface(), error: err.message });
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
async attemptStartRequest() {
|
|
195
|
+
try {
|
|
196
|
+
const started = await this.getSensor().startRequest();
|
|
197
|
+
this.logEvent({ message: started ? 'start request acknowledged' : 'start request not acknowledged (non-fatal)', device: this.getName(), interface: this.getInterface() });
|
|
198
|
+
}
|
|
199
|
+
catch (err) {
|
|
200
|
+
this.logEvent({ message: 'start request failed (non-fatal)', device: this.getName(), interface: this.getInterface(), error: err.message });
|
|
201
|
+
}
|
|
202
|
+
}
|
|
173
203
|
setConstants() {
|
|
174
204
|
const mode = this.getCyclingMode();
|
|
175
205
|
const sensor = this.getSensor();
|
|
@@ -195,7 +225,7 @@ class BleFmAdapter extends adapter_js_1.default {
|
|
|
195
225
|
let hasControl = false;
|
|
196
226
|
let tryCnt = 0;
|
|
197
227
|
const sensor = this.getSensor();
|
|
198
|
-
|
|
228
|
+
const controlPromise = new Promise((resolve) => {
|
|
199
229
|
this.startTask.notifyOnStop(() => {
|
|
200
230
|
resolve(false);
|
|
201
231
|
});
|
|
@@ -219,6 +249,18 @@ class BleFmAdapter extends adapter_js_1.default {
|
|
|
219
249
|
};
|
|
220
250
|
waitUntilControl();
|
|
221
251
|
});
|
|
252
|
+
const waitTask = new task_js_1.InteruptableTask(controlPromise, {
|
|
253
|
+
timeout: this.requestControlTimeout,
|
|
254
|
+
name: 'establishControl',
|
|
255
|
+
errorOnTimeout: false,
|
|
256
|
+
log: this.logEvent.bind(this)
|
|
257
|
+
});
|
|
258
|
+
const granted = await waitTask.run();
|
|
259
|
+
if (!granted && this.isStarting()) {
|
|
260
|
+
this.logEvent({ message: 'could not establish control', device: this.getName(), interface: this.getInterface() });
|
|
261
|
+
throw new Error('could not establish control');
|
|
262
|
+
}
|
|
263
|
+
return granted;
|
|
222
264
|
}
|
|
223
265
|
async sendInitialRequest() {
|
|
224
266
|
const startRequest = this.getCyclingMode().getBikeInitRequest();
|
package/lib/cjs/ble/fm/sensor.js
CHANGED
|
@@ -111,9 +111,6 @@ class BleFitnessMachineDevice extends sensor_js_1.TBleSensor {
|
|
|
111
111
|
}
|
|
112
112
|
if (!this.isSubscribed())
|
|
113
113
|
return false;
|
|
114
|
-
if (this.features?.setPower === false && this.features?.setSlope === false && this.features?.setResistance === false) {
|
|
115
|
-
return true;
|
|
116
|
-
}
|
|
117
114
|
this.logEvent({ message: 'requestControl' });
|
|
118
115
|
this.isCheckingControl = true;
|
|
119
116
|
const data = Buffer.alloc(1);
|
|
@@ -72,14 +72,18 @@ class DirectConnectInterface extends node_events_1.EventEmitter {
|
|
|
72
72
|
const protocol = this.getProtocol(service);
|
|
73
73
|
const address = service.address;
|
|
74
74
|
const services = service.serviceUUIDs?.map(uuid => (0, index_js_1.beautifyUUID)(uuid, false))?.join(',');
|
|
75
|
-
|
|
75
|
+
const id = service.serialNo || undefined;
|
|
76
|
+
return { interface: DirectConnectInterface.INTERFACE_NAME, name, protocol, address, services, ...(id ? { id } : {}) };
|
|
76
77
|
}
|
|
77
78
|
catch {
|
|
78
79
|
return null;
|
|
79
80
|
}
|
|
80
81
|
}
|
|
81
82
|
createPeripheralFromSettings(settings) {
|
|
82
|
-
const
|
|
83
|
+
const bleSettings = settings;
|
|
84
|
+
const all = this.getAll();
|
|
85
|
+
const info = (bleSettings.address && all.find(a => a.service.name === settings.name && a.service.address === bleSettings.address))
|
|
86
|
+
?? all.find(a => a.service.name === settings.name);
|
|
83
87
|
if (!info?.service)
|
|
84
88
|
return null;
|
|
85
89
|
return this.createPeripheral(info.service);
|
|
@@ -247,35 +251,68 @@ class DirectConnectInterface extends node_events_1.EventEmitter {
|
|
|
247
251
|
const src = source === 'known-device' ? 'known-device' : 'mdns';
|
|
248
252
|
try {
|
|
249
253
|
service.transport = this.getName();
|
|
250
|
-
const
|
|
251
|
-
if (
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
254
|
+
const sameAddress = this.findByNameAndAddress(service.name, service.address);
|
|
255
|
+
if (sameAddress) {
|
|
256
|
+
this.refreshSameAddressEntry(sameAddress, service, src, source);
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
const sameName = this.findAllByName(service.name);
|
|
260
|
+
if (src === 'known-device' && sameName.some(a => a.source !== 'known-device')) {
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
if (sameName.length === 0) {
|
|
264
|
+
this.announceNewEntry(service, src, source, 'device announced');
|
|
265
|
+
return;
|
|
259
266
|
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
this.logEvent({ message: 'device announced', device: service.name, announcement: service, source });
|
|
265
|
-
this.emitDevice(service);
|
|
266
|
-
this.matching?.push(service.name);
|
|
267
|
+
const realEntries = sameName.filter(a => a.source !== 'known-device');
|
|
268
|
+
if (src !== 'known-device' && realEntries.length >= 1) {
|
|
269
|
+
this.announceNewEntry(service, src, source, 'device announced (name collision, distinct address)');
|
|
270
|
+
return;
|
|
267
271
|
}
|
|
272
|
+
this.reannounceExistingEntry(sameName[0], service, src, source);
|
|
268
273
|
}
|
|
269
274
|
catch (err) {
|
|
270
275
|
this.logError(err, 'addService');
|
|
271
276
|
}
|
|
272
277
|
}
|
|
273
|
-
|
|
274
|
-
|
|
278
|
+
refreshSameAddressEntry(sameAddress, service, src, source) {
|
|
279
|
+
const idx = this.services.indexOf(sameAddress);
|
|
280
|
+
const wasKnownDevicePlaceholder = sameAddress.source === 'known-device';
|
|
281
|
+
const nextSource = (wasKnownDevicePlaceholder && src !== 'known-device') ? src : sameAddress.source;
|
|
282
|
+
this.services[idx] = { ts: Date.now(), service, source: nextSource };
|
|
283
|
+
if (src !== 'known-device' && wasKnownDevicePlaceholder) {
|
|
284
|
+
this.logEvent({ message: 'device re-announced', device: service.name, announcement: service, source });
|
|
285
|
+
this.emitDevice(service);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
announceNewEntry(service, src, source, message) {
|
|
289
|
+
this.services.push({ ts: Date.now(), service, source: src });
|
|
290
|
+
if (!service.serviceUUIDs?.length)
|
|
291
|
+
return;
|
|
292
|
+
this.logEvent({ message, device: service.name, announcement: service, source });
|
|
293
|
+
this.emitDevice(service);
|
|
294
|
+
this.matching?.push(service.name);
|
|
295
|
+
}
|
|
296
|
+
reannounceExistingEntry(existing, service, src, source) {
|
|
297
|
+
const idx = this.services.indexOf(existing);
|
|
298
|
+
this.services[idx] = { ts: Date.now(), service, source: src };
|
|
299
|
+
if (src !== 'known-device') {
|
|
300
|
+
this.logEvent({ message: 'device re-announced', device: service.name, announcement: service, source });
|
|
301
|
+
this.emitDevice(service);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
findByNameAndAddress(name, address) {
|
|
305
|
+
return this.services.find(a => a.service.name === name && a.service.address === address && a.ts > Date.now() - DC_EXPIRATION_TIMEOUT);
|
|
306
|
+
}
|
|
307
|
+
findAllByName(name) {
|
|
308
|
+
return this.services.filter(a => a.service.name === name && a.ts > Date.now() - DC_EXPIRATION_TIMEOUT);
|
|
275
309
|
}
|
|
276
310
|
getAll() {
|
|
277
311
|
return this.services.filter(a => a.ts > Date.now() - DC_EXPIRATION_TIMEOUT);
|
|
278
312
|
}
|
|
313
|
+
hasNameCollision(name) {
|
|
314
|
+
return this.findAllByName(name).length > 1;
|
|
315
|
+
}
|
|
279
316
|
setDebug(enabled) {
|
|
280
317
|
this.debug = enabled;
|
|
281
318
|
}
|
|
@@ -50,7 +50,7 @@ export default class BleAdapter extends IncyclistDevice {
|
|
|
50
50
|
const info = peripheral.getInfo();
|
|
51
51
|
const settings = { ...this.settings };
|
|
52
52
|
settings.id = settings.id ?? info.id;
|
|
53
|
-
settings.address =
|
|
53
|
+
settings.address = info.address ?? settings.address;
|
|
54
54
|
settings.name = settings.name ?? info.name;
|
|
55
55
|
this.settings = settings;
|
|
56
56
|
}
|
|
@@ -72,11 +72,13 @@ export default class BleAdapter extends IncyclistDevice {
|
|
|
72
72
|
if (as.profile || settings.profile) {
|
|
73
73
|
return (as.protocol === settings.protocol && as.profile === settings.profile && as.name === settings.name);
|
|
74
74
|
}
|
|
75
|
-
|
|
76
|
-
return
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
75
|
+
if (as.protocol !== settings.protocol)
|
|
76
|
+
return false;
|
|
77
|
+
if (as.id && settings.id)
|
|
78
|
+
return as.id === settings.id;
|
|
79
|
+
if (as.address && settings.address)
|
|
80
|
+
return as.address === settings.address;
|
|
81
|
+
return !!(as.name && settings.name && as.name === settings.name);
|
|
80
82
|
}
|
|
81
83
|
isSame(adapter) {
|
|
82
84
|
return this.isEqual(adapter.getSettings());
|
|
@@ -246,6 +248,8 @@ export default class BleAdapter extends IncyclistDevice {
|
|
|
246
248
|
}
|
|
247
249
|
async initControl(_props) {
|
|
248
250
|
}
|
|
251
|
+
async initControlBestEffort(_props) {
|
|
252
|
+
}
|
|
249
253
|
getStartLogProps(props) {
|
|
250
254
|
const capabilities = this.props?.capabilities;
|
|
251
255
|
const { user, userWeight, bikeWeight, timeout, wheelDiameter, restart, scanOnly } = props ?? {};
|
|
@@ -281,11 +285,16 @@ export default class BleAdapter extends IncyclistDevice {
|
|
|
281
285
|
this.stopped = true;
|
|
282
286
|
return false;
|
|
283
287
|
}
|
|
284
|
-
await this.waitForInitialData(timeout);
|
|
285
288
|
await this.checkCapabilities();
|
|
286
289
|
const skipControl = this.props.capabilities && !this.props.capabilities.includes(IncyclistCapability.Control);
|
|
287
|
-
|
|
290
|
+
const isControllable = this.hasCapability(IncyclistCapability.Control) && !skipControl;
|
|
291
|
+
if (isControllable) {
|
|
288
292
|
await this.initControl(startProps);
|
|
293
|
+
}
|
|
294
|
+
else {
|
|
295
|
+
await this.initControlBestEffort(startProps);
|
|
296
|
+
}
|
|
297
|
+
await this.waitForInitialData(timeout);
|
|
289
298
|
this.stopped = false;
|
|
290
299
|
this.started = true;
|
|
291
300
|
if (wasPaused)
|
|
@@ -7,6 +7,7 @@ import BleAdapter from '../base/adapter.js';
|
|
|
7
7
|
import { cRR, cwABike } from './consts.js';
|
|
8
8
|
import { IncyclistCapability } from '../../types/index.js';
|
|
9
9
|
import { sleep } from '../../utils/utils.js';
|
|
10
|
+
import { InteruptableTask } from '../../utils/task.js';
|
|
10
11
|
import { BleZwiftPlaySensor } from '../zwift/play/index.js';
|
|
11
12
|
import { useFeatureToggle } from '../../features/index.js';
|
|
12
13
|
import FMResistanceMode from '../../modes/fm-resistance.js';
|
|
@@ -16,6 +17,7 @@ export default class BleFmAdapter extends BleAdapter {
|
|
|
16
17
|
distanceInternal = 0;
|
|
17
18
|
connectPromise;
|
|
18
19
|
requestControlRetryDelay = 1000;
|
|
20
|
+
requestControlTimeout = 10000;
|
|
19
21
|
promiseSendUpdate;
|
|
20
22
|
zwiftPlay;
|
|
21
23
|
isHubInitialized = false;
|
|
@@ -163,8 +165,36 @@ export default class BleFmAdapter extends BleAdapter {
|
|
|
163
165
|
if (!this.hasCapability(IncyclistCapability.Control))
|
|
164
166
|
return;
|
|
165
167
|
await this.establishControl();
|
|
168
|
+
await this.attemptStartRequest();
|
|
166
169
|
await this.sendInitialRequest();
|
|
167
170
|
}
|
|
171
|
+
async initControlBestEffort(_startProps) {
|
|
172
|
+
if (!this.isStarting())
|
|
173
|
+
return;
|
|
174
|
+
this.setConstants();
|
|
175
|
+
try {
|
|
176
|
+
const hasControl = await this.getSensor().requestControl();
|
|
177
|
+
if (hasControl) {
|
|
178
|
+
this.logEvent({ message: 'control granted (downgraded device)', device: this.getName(), interface: this.getInterface() });
|
|
179
|
+
await this.attemptStartRequest();
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
this.logEvent({ message: 'could not establish control (downgraded device, non-fatal)', device: this.getName(), interface: this.getInterface() });
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
catch (err) {
|
|
186
|
+
this.logEvent({ message: 'could not establish control (downgraded device, non-fatal)', device: this.getName(), interface: this.getInterface(), error: err.message });
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
async attemptStartRequest() {
|
|
190
|
+
try {
|
|
191
|
+
const started = await this.getSensor().startRequest();
|
|
192
|
+
this.logEvent({ message: started ? 'start request acknowledged' : 'start request not acknowledged (non-fatal)', device: this.getName(), interface: this.getInterface() });
|
|
193
|
+
}
|
|
194
|
+
catch (err) {
|
|
195
|
+
this.logEvent({ message: 'start request failed (non-fatal)', device: this.getName(), interface: this.getInterface(), error: err.message });
|
|
196
|
+
}
|
|
197
|
+
}
|
|
168
198
|
setConstants() {
|
|
169
199
|
const mode = this.getCyclingMode();
|
|
170
200
|
const sensor = this.getSensor();
|
|
@@ -190,7 +220,7 @@ export default class BleFmAdapter extends BleAdapter {
|
|
|
190
220
|
let hasControl = false;
|
|
191
221
|
let tryCnt = 0;
|
|
192
222
|
const sensor = this.getSensor();
|
|
193
|
-
|
|
223
|
+
const controlPromise = new Promise((resolve) => {
|
|
194
224
|
this.startTask.notifyOnStop(() => {
|
|
195
225
|
resolve(false);
|
|
196
226
|
});
|
|
@@ -214,6 +244,18 @@ export default class BleFmAdapter extends BleAdapter {
|
|
|
214
244
|
};
|
|
215
245
|
waitUntilControl();
|
|
216
246
|
});
|
|
247
|
+
const waitTask = new InteruptableTask(controlPromise, {
|
|
248
|
+
timeout: this.requestControlTimeout,
|
|
249
|
+
name: 'establishControl',
|
|
250
|
+
errorOnTimeout: false,
|
|
251
|
+
log: this.logEvent.bind(this)
|
|
252
|
+
});
|
|
253
|
+
const granted = await waitTask.run();
|
|
254
|
+
if (!granted && this.isStarting()) {
|
|
255
|
+
this.logEvent({ message: 'could not establish control', device: this.getName(), interface: this.getInterface() });
|
|
256
|
+
throw new Error('could not establish control');
|
|
257
|
+
}
|
|
258
|
+
return granted;
|
|
217
259
|
}
|
|
218
260
|
async sendInitialRequest() {
|
|
219
261
|
const startRequest = this.getCyclingMode().getBikeInitRequest();
|
package/lib/esm/ble/fm/sensor.js
CHANGED
|
@@ -109,9 +109,6 @@ export default class BleFitnessMachineDevice extends TBleSensor {
|
|
|
109
109
|
}
|
|
110
110
|
if (!this.isSubscribed())
|
|
111
111
|
return false;
|
|
112
|
-
if (this.features?.setPower === false && this.features?.setSlope === false && this.features?.setResistance === false) {
|
|
113
|
-
return true;
|
|
114
|
-
}
|
|
115
112
|
this.logEvent({ message: 'requestControl' });
|
|
116
113
|
this.isCheckingControl = true;
|
|
117
114
|
const data = Buffer.alloc(1);
|
|
@@ -69,14 +69,18 @@ export default class DirectConnectInterface extends EventEmitter {
|
|
|
69
69
|
const protocol = this.getProtocol(service);
|
|
70
70
|
const address = service.address;
|
|
71
71
|
const services = service.serviceUUIDs?.map(uuid => beautifyUUID(uuid, false))?.join(',');
|
|
72
|
-
|
|
72
|
+
const id = service.serialNo || undefined;
|
|
73
|
+
return { interface: DirectConnectInterface.INTERFACE_NAME, name, protocol, address, services, ...(id ? { id } : {}) };
|
|
73
74
|
}
|
|
74
75
|
catch {
|
|
75
76
|
return null;
|
|
76
77
|
}
|
|
77
78
|
}
|
|
78
79
|
createPeripheralFromSettings(settings) {
|
|
79
|
-
const
|
|
80
|
+
const bleSettings = settings;
|
|
81
|
+
const all = this.getAll();
|
|
82
|
+
const info = (bleSettings.address && all.find(a => a.service.name === settings.name && a.service.address === bleSettings.address))
|
|
83
|
+
?? all.find(a => a.service.name === settings.name);
|
|
80
84
|
if (!info?.service)
|
|
81
85
|
return null;
|
|
82
86
|
return this.createPeripheral(info.service);
|
|
@@ -244,35 +248,68 @@ export default class DirectConnectInterface extends EventEmitter {
|
|
|
244
248
|
const src = source === 'known-device' ? 'known-device' : 'mdns';
|
|
245
249
|
try {
|
|
246
250
|
service.transport = this.getName();
|
|
247
|
-
const
|
|
248
|
-
if (
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
251
|
+
const sameAddress = this.findByNameAndAddress(service.name, service.address);
|
|
252
|
+
if (sameAddress) {
|
|
253
|
+
this.refreshSameAddressEntry(sameAddress, service, src, source);
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
const sameName = this.findAllByName(service.name);
|
|
257
|
+
if (src === 'known-device' && sameName.some(a => a.source !== 'known-device')) {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
if (sameName.length === 0) {
|
|
261
|
+
this.announceNewEntry(service, src, source, 'device announced');
|
|
262
|
+
return;
|
|
256
263
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
this.logEvent({ message: 'device announced', device: service.name, announcement: service, source });
|
|
262
|
-
this.emitDevice(service);
|
|
263
|
-
this.matching?.push(service.name);
|
|
264
|
+
const realEntries = sameName.filter(a => a.source !== 'known-device');
|
|
265
|
+
if (src !== 'known-device' && realEntries.length >= 1) {
|
|
266
|
+
this.announceNewEntry(service, src, source, 'device announced (name collision, distinct address)');
|
|
267
|
+
return;
|
|
264
268
|
}
|
|
269
|
+
this.reannounceExistingEntry(sameName[0], service, src, source);
|
|
265
270
|
}
|
|
266
271
|
catch (err) {
|
|
267
272
|
this.logError(err, 'addService');
|
|
268
273
|
}
|
|
269
274
|
}
|
|
270
|
-
|
|
271
|
-
|
|
275
|
+
refreshSameAddressEntry(sameAddress, service, src, source) {
|
|
276
|
+
const idx = this.services.indexOf(sameAddress);
|
|
277
|
+
const wasKnownDevicePlaceholder = sameAddress.source === 'known-device';
|
|
278
|
+
const nextSource = (wasKnownDevicePlaceholder && src !== 'known-device') ? src : sameAddress.source;
|
|
279
|
+
this.services[idx] = { ts: Date.now(), service, source: nextSource };
|
|
280
|
+
if (src !== 'known-device' && wasKnownDevicePlaceholder) {
|
|
281
|
+
this.logEvent({ message: 'device re-announced', device: service.name, announcement: service, source });
|
|
282
|
+
this.emitDevice(service);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
announceNewEntry(service, src, source, message) {
|
|
286
|
+
this.services.push({ ts: Date.now(), service, source: src });
|
|
287
|
+
if (!service.serviceUUIDs?.length)
|
|
288
|
+
return;
|
|
289
|
+
this.logEvent({ message, device: service.name, announcement: service, source });
|
|
290
|
+
this.emitDevice(service);
|
|
291
|
+
this.matching?.push(service.name);
|
|
292
|
+
}
|
|
293
|
+
reannounceExistingEntry(existing, service, src, source) {
|
|
294
|
+
const idx = this.services.indexOf(existing);
|
|
295
|
+
this.services[idx] = { ts: Date.now(), service, source: src };
|
|
296
|
+
if (src !== 'known-device') {
|
|
297
|
+
this.logEvent({ message: 'device re-announced', device: service.name, announcement: service, source });
|
|
298
|
+
this.emitDevice(service);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
findByNameAndAddress(name, address) {
|
|
302
|
+
return this.services.find(a => a.service.name === name && a.service.address === address && a.ts > Date.now() - DC_EXPIRATION_TIMEOUT);
|
|
303
|
+
}
|
|
304
|
+
findAllByName(name) {
|
|
305
|
+
return this.services.filter(a => a.service.name === name && a.ts > Date.now() - DC_EXPIRATION_TIMEOUT);
|
|
272
306
|
}
|
|
273
307
|
getAll() {
|
|
274
308
|
return this.services.filter(a => a.ts > Date.now() - DC_EXPIRATION_TIMEOUT);
|
|
275
309
|
}
|
|
310
|
+
hasNameCollision(name) {
|
|
311
|
+
return this.findAllByName(name).length > 1;
|
|
312
|
+
}
|
|
276
313
|
setDebug(enabled) {
|
|
277
314
|
this.debug = enabled;
|
|
278
315
|
}
|
|
@@ -48,6 +48,7 @@ export default class BleAdapter<TDeviceData extends BleDeviceData, TDevice exten
|
|
|
48
48
|
protected waitForInitialData(startupTimeout: any): Promise<void>;
|
|
49
49
|
protected checkCapabilities(): Promise<void>;
|
|
50
50
|
protected initControl(_props?: BleStartProperties): Promise<void>;
|
|
51
|
+
protected initControlBestEffort(_props?: BleStartProperties): Promise<void>;
|
|
51
52
|
protected getStartLogProps(props: BleStartProperties): BleStartProperties;
|
|
52
53
|
protected startAdapter(startProps?: BleStartProperties): Promise<boolean>;
|
|
53
54
|
startSensor(): Promise<boolean>;
|
|
@@ -12,6 +12,7 @@ export default class BleFmAdapter extends BleAdapter<IndoorBikeData, BleFitnessM
|
|
|
12
12
|
protected distanceInternal: number;
|
|
13
13
|
protected connectPromise: Promise<boolean> | undefined;
|
|
14
14
|
protected requestControlRetryDelay: number;
|
|
15
|
+
protected requestControlTimeout: number;
|
|
15
16
|
protected promiseSendUpdate: Promise<UpdateRequest | void> | undefined;
|
|
16
17
|
protected zwiftPlay: BleZwiftPlaySensor | undefined;
|
|
17
18
|
protected isHubInitialized: boolean;
|
|
@@ -28,6 +29,8 @@ export default class BleFmAdapter extends BleAdapter<IndoorBikeData, BleFitnessM
|
|
|
28
29
|
protected checkResume(): boolean[];
|
|
29
30
|
protected initVirtualShifting(initHub?: boolean): Promise<void>;
|
|
30
31
|
protected initControl(_startProps?: BleStartProperties): Promise<void>;
|
|
32
|
+
protected initControlBestEffort(_startProps?: BleStartProperties): Promise<void>;
|
|
33
|
+
protected attemptStartRequest(): Promise<void>;
|
|
31
34
|
protected setConstants(): void;
|
|
32
35
|
protected establishControl(): Promise<boolean>;
|
|
33
36
|
protected sendInitialRequest(): Promise<void>;
|
|
@@ -62,8 +62,13 @@ export default class DirectConnectInterface extends EventEmitter implements IBle
|
|
|
62
62
|
name: string;
|
|
63
63
|
}[];
|
|
64
64
|
protected addService(service: MulticastDnsAnnouncement, source?: string): void;
|
|
65
|
-
|
|
65
|
+
private refreshSameAddressEntry;
|
|
66
|
+
private announceNewEntry;
|
|
67
|
+
private reannounceExistingEntry;
|
|
68
|
+
protected findByNameAndAddress(name: string, address: string): Announcement;
|
|
69
|
+
protected findAllByName(name: string): Announcement[];
|
|
66
70
|
protected getAll(): Announcement[];
|
|
71
|
+
hasNameCollision(name: string): boolean;
|
|
67
72
|
setDebug(enabled: boolean): void;
|
|
68
73
|
logEvent(event: any): void;
|
|
69
74
|
logError(err: Error, fn: string, args?: any): void;
|