@mentra/bluetooth-sdk 3.1.0-dev.16 → 3.1.0-dev.17
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/android/src/main/java/com/mentra/bluetoothsdk/GeneratedReleaseMetadata.kt +4 -4
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +9 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.kt +55 -16
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/MentraLivePairingAdvertisementParserTest.kt +58 -0
- package/build/generated/releaseMetadata.js +4 -4
- package/build/generated/releaseMetadata.js.map +1 -1
- package/ios/Source/BluetoothSdkDefaults.swift +1 -1
- package/ios/Source/GeneratedReleaseMetadata.swift +4 -4
- package/ios/Source/MentraBluetoothSDK.swift +9 -0
- package/ios/Source/sgcs/MentraLive.swift +53 -12
- package/ios/Tests/MentraLivePairingAdvertisementTests.swift +56 -0
- package/package.json +1 -1
- package/src/generated/releaseMetadata.ts +4 -4
|
@@ -3,9 +3,9 @@ package com.mentra.bluetoothsdk
|
|
|
3
3
|
/** Generated by release CI. Do not edit in a release checkout. */
|
|
4
4
|
internal object GeneratedReleaseMetadata {
|
|
5
5
|
const val FAMILY_BASE_VERSION: String = "3.1.0"
|
|
6
|
-
const val RELEASE_IDENTITY: String = "3.1.0-dev.
|
|
7
|
-
const val RELEASE_SET_ID: String = "mentra-3.1.0-dev.
|
|
8
|
-
const val SOURCE_COMMIT: String = "
|
|
9
|
-
const val OTA_MANIFEST_URL: String = "https://github.com/Mentra-Community/MentraOS/releases/download/mentra-v3.1.0-dev.
|
|
6
|
+
const val RELEASE_IDENTITY: String = "3.1.0-dev.17"
|
|
7
|
+
const val RELEASE_SET_ID: String = "mentra-3.1.0-dev.17"
|
|
8
|
+
const val SOURCE_COMMIT: String = "84c1e737fddc23bf9abb9b1916937c5b7e892f54"
|
|
9
|
+
const val OTA_MANIFEST_URL: String = "https://github.com/Mentra-Community/MentraOS/releases/download/mentra-v3.1.0-dev.17/mentra-live-ota-3.1.0-dev.17.json"
|
|
10
10
|
const val OTA_MANIFEST_SHA256: String = "47e576dec6afd4cc6ccbc758b12fe65b33b180382a899f8098dfcd11f7bcb2a6"
|
|
11
11
|
}
|
|
@@ -284,6 +284,7 @@ class MentraBluetoothSdk private constructor(
|
|
|
284
284
|
DeviceStore.apply(ObservableStore.BLUETOOTH_CATEGORY, "project_name", device.projectName ?: "")
|
|
285
285
|
DeviceStore.apply(ObservableStore.BLUETOOTH_CATEGORY, "pending_device_name", "")
|
|
286
286
|
DeviceStore.apply(ObservableStore.BLUETOOTH_CATEGORY, "pending_device_address", "")
|
|
287
|
+
DeviceStore.apply(ObservableStore.BLUETOOTH_CATEGORY, "pending_device_secure_pairing_capable", "")
|
|
287
288
|
} finally {
|
|
288
289
|
suppressDefaultDeviceEvents = false
|
|
289
290
|
}
|
|
@@ -299,6 +300,7 @@ class MentraBluetoothSdk private constructor(
|
|
|
299
300
|
DeviceStore.apply(ObservableStore.BLUETOOTH_CATEGORY, "project_name", "")
|
|
300
301
|
DeviceStore.apply(ObservableStore.BLUETOOTH_CATEGORY, "pending_device_name", "")
|
|
301
302
|
DeviceStore.apply(ObservableStore.BLUETOOTH_CATEGORY, "pending_device_address", "")
|
|
303
|
+
DeviceStore.apply(ObservableStore.BLUETOOTH_CATEGORY, "pending_device_secure_pairing_capable", "")
|
|
302
304
|
} finally {
|
|
303
305
|
suppressDefaultDeviceEvents = false
|
|
304
306
|
}
|
|
@@ -428,6 +430,11 @@ class MentraBluetoothSdk private constructor(
|
|
|
428
430
|
"pending_device_address",
|
|
429
431
|
device.address ?: "",
|
|
430
432
|
)
|
|
433
|
+
DeviceStore.apply(
|
|
434
|
+
ObservableStore.BLUETOOTH_CATEGORY,
|
|
435
|
+
"pending_device_secure_pairing_capable",
|
|
436
|
+
device.securePairingCapable ?: "",
|
|
437
|
+
)
|
|
431
438
|
}
|
|
432
439
|
}
|
|
433
440
|
DeviceStore.apply(ObservableStore.BLUETOOTH_CATEGORY, "pending_wearable", device.model.deviceType)
|
|
@@ -454,6 +461,7 @@ class MentraBluetoothSdk private constructor(
|
|
|
454
461
|
fun cancelConnectionAttempt() {
|
|
455
462
|
DeviceStore.apply(ObservableStore.BLUETOOTH_CATEGORY, "pending_device_name", "")
|
|
456
463
|
DeviceStore.apply(ObservableStore.BLUETOOTH_CATEGORY, "pending_device_address", "")
|
|
464
|
+
DeviceStore.apply(ObservableStore.BLUETOOTH_CATEGORY, "pending_device_secure_pairing_capable", "")
|
|
457
465
|
deviceManager.disconnect()
|
|
458
466
|
}
|
|
459
467
|
|
|
@@ -464,6 +472,7 @@ class MentraBluetoothSdk private constructor(
|
|
|
464
472
|
fun disconnect() {
|
|
465
473
|
DeviceStore.apply(ObservableStore.BLUETOOTH_CATEGORY, "pending_device_name", "")
|
|
466
474
|
DeviceStore.apply(ObservableStore.BLUETOOTH_CATEGORY, "pending_device_address", "")
|
|
475
|
+
DeviceStore.apply(ObservableStore.BLUETOOTH_CATEGORY, "pending_device_secure_pairing_capable", "")
|
|
467
476
|
deviceManager.disconnect()
|
|
468
477
|
}
|
|
469
478
|
|
|
@@ -82,6 +82,33 @@ internal fun hasRequiredMentraLiveCoreCharacteristics(
|
|
|
82
82
|
hasTxCharacteristic: Boolean,
|
|
83
83
|
): Boolean = hasRxCharacteristic && hasTxCharacteristic
|
|
84
84
|
|
|
85
|
+
internal fun isPendingMentraLivePairingTarget(
|
|
86
|
+
connectedName: String,
|
|
87
|
+
connectedAddress: String,
|
|
88
|
+
pendingName: String,
|
|
89
|
+
pendingAddress: String,
|
|
90
|
+
): Boolean {
|
|
91
|
+
if (pendingAddress.isNotEmpty()) {
|
|
92
|
+
return connectedAddress.equals(pendingAddress, ignoreCase = true)
|
|
93
|
+
}
|
|
94
|
+
return pendingName.isNotEmpty() && connectedName == pendingName
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
internal fun shouldRecoverConnectedMentraLivePairingTarget(
|
|
98
|
+
isConnected: Boolean,
|
|
99
|
+
connectedName: String,
|
|
100
|
+
connectedAddress: String,
|
|
101
|
+
pendingName: String,
|
|
102
|
+
pendingAddress: String,
|
|
103
|
+
): Boolean =
|
|
104
|
+
isConnected &&
|
|
105
|
+
isPendingMentraLivePairingTarget(
|
|
106
|
+
connectedName,
|
|
107
|
+
connectedAddress,
|
|
108
|
+
pendingName,
|
|
109
|
+
pendingAddress,
|
|
110
|
+
)
|
|
111
|
+
|
|
85
112
|
/**
|
|
86
113
|
* Smart Glasses Communicator for Mentra Live (K900) glasses Uses BLE to communicate with the
|
|
87
114
|
* glasses
|
|
@@ -1265,11 +1292,6 @@ class MentraLive : SGCManager() {
|
|
|
1265
1292
|
val generation = ++scanGeneration
|
|
1266
1293
|
isScanning = true
|
|
1267
1294
|
bluetoothScanner!!.startScan(filters, settings, scanCallback)
|
|
1268
|
-
// Connected glasses stop advertising (BLE_CONNECTION_MAX=1). Pairing scan
|
|
1269
|
-
// would otherwise return empty even though GATT is already up.
|
|
1270
|
-
if (!isReconnecting) {
|
|
1271
|
-
handler.post { emitConnectedDeviceForPairingScan() }
|
|
1272
|
-
}
|
|
1273
1295
|
|
|
1274
1296
|
// Set a timeout to stop scanning
|
|
1275
1297
|
handler.postDelayed(
|
|
@@ -1352,29 +1374,44 @@ class MentraLive : SGCManager() {
|
|
|
1352
1374
|
}
|
|
1353
1375
|
|
|
1354
1376
|
/**
|
|
1355
|
-
*
|
|
1356
|
-
*
|
|
1357
|
-
*
|
|
1377
|
+
* A selected pairing target can stop advertising once its GATT link comes up, before
|
|
1378
|
+
* readiness promotes it to the default device. Re-emit only that explicit pending target;
|
|
1379
|
+
* an established owner's connected glasses have no pending name/address and stay hidden.
|
|
1358
1380
|
*/
|
|
1359
|
-
private fun
|
|
1360
|
-
if (!
|
|
1361
|
-
return
|
|
1362
|
-
}
|
|
1381
|
+
private fun emitConnectedPendingDeviceForPairingScan() {
|
|
1382
|
+
if (!manualDiscoveryActive || isReconnecting || pairingYieldActive) return
|
|
1363
1383
|
val device = connectedDevice ?: bluetoothGatt?.device ?: return
|
|
1364
1384
|
val name = safeBondedDeviceName(device)
|
|
1365
|
-
if (!isMentraLiveBluetoothName(name))
|
|
1385
|
+
if (!isMentraLiveBluetoothName(name)) return
|
|
1386
|
+
|
|
1387
|
+
val pendingName = DeviceStore.get("bluetooth", "pending_device_name") as? String ?: ""
|
|
1388
|
+
val pendingAddress =
|
|
1389
|
+
DeviceStore.get("bluetooth", "pending_device_address") as? String ?: ""
|
|
1390
|
+
val pendingSecurePairingCapable =
|
|
1391
|
+
DeviceStore.get("bluetooth", "pending_device_secure_pairing_capable") as? Boolean
|
|
1392
|
+
val address = device.address ?: ""
|
|
1393
|
+
if (
|
|
1394
|
+
!shouldRecoverConnectedMentraLivePairingTarget(
|
|
1395
|
+
isConnected,
|
|
1396
|
+
name,
|
|
1397
|
+
address,
|
|
1398
|
+
pendingName,
|
|
1399
|
+
pendingAddress,
|
|
1400
|
+
)
|
|
1401
|
+
) {
|
|
1366
1402
|
return
|
|
1367
1403
|
}
|
|
1404
|
+
|
|
1368
1405
|
Bridge.log(
|
|
1369
|
-
"LIVE: Pairing scan:
|
|
1406
|
+
"LIVE: Pairing scan: recovering connected pending target $name ($address)"
|
|
1370
1407
|
)
|
|
1371
1408
|
Bridge.sendDiscoveredDevice(
|
|
1372
1409
|
DeviceTypes.LIVE,
|
|
1373
1410
|
name,
|
|
1374
|
-
|
|
1411
|
+
address,
|
|
1375
1412
|
pairingMode = true,
|
|
1376
1413
|
pairingCode = null,
|
|
1377
|
-
securePairingCapable =
|
|
1414
|
+
securePairingCapable = pendingSecurePairingCapable,
|
|
1378
1415
|
)
|
|
1379
1416
|
}
|
|
1380
1417
|
|
|
@@ -2075,6 +2112,7 @@ class MentraLive : SGCManager() {
|
|
|
2075
2112
|
isConnecting = false
|
|
2076
2113
|
isConnected = true
|
|
2077
2114
|
connectedDevice = gatt.device
|
|
2115
|
+
emitConnectedPendingDeviceForPairingScan()
|
|
2078
2116
|
|
|
2079
2117
|
DeviceStore.apply("glasses", "bluetoothName", connectedDevice!!.name)
|
|
2080
2118
|
// Persist MAC so reconnection can use direct GATT instead of scanning
|
|
@@ -5919,6 +5957,7 @@ class MentraLive : SGCManager() {
|
|
|
5919
5957
|
|
|
5920
5958
|
// Start scanning for BLE devices
|
|
5921
5959
|
startScan()
|
|
5960
|
+
handler.post { emitConnectedPendingDeviceForPairingScan() }
|
|
5922
5961
|
}
|
|
5923
5962
|
|
|
5924
5963
|
private fun releaseStaleClassicForDiscovery() {
|
|
@@ -7,6 +7,64 @@ import org.junit.Assert.assertTrue
|
|
|
7
7
|
import org.junit.Test
|
|
8
8
|
|
|
9
9
|
class MentraLivePairingAdvertisementParserTest {
|
|
10
|
+
@Test
|
|
11
|
+
fun connectedPairingRecoveryRequiresExplicitPendingTarget() {
|
|
12
|
+
assertFalse(
|
|
13
|
+
isPendingMentraLivePairingTarget(
|
|
14
|
+
connectedName = "MENTRA_LIVE_BLE_OWNER",
|
|
15
|
+
connectedAddress = "AA:BB:CC:DD:EE:FF",
|
|
16
|
+
pendingName = "",
|
|
17
|
+
pendingAddress = "",
|
|
18
|
+
)
|
|
19
|
+
)
|
|
20
|
+
assertTrue(
|
|
21
|
+
isPendingMentraLivePairingTarget(
|
|
22
|
+
connectedName = "MENTRA_LIVE_BLE_TARGET",
|
|
23
|
+
connectedAddress = "AA:BB:CC:DD:EE:FF",
|
|
24
|
+
pendingName = "MENTRA_LIVE_BLE_TARGET",
|
|
25
|
+
pendingAddress = "",
|
|
26
|
+
)
|
|
27
|
+
)
|
|
28
|
+
assertTrue(
|
|
29
|
+
isPendingMentraLivePairingTarget(
|
|
30
|
+
connectedName = "MENTRA_LIVE_BLE_TARGET",
|
|
31
|
+
connectedAddress = "AA:BB:CC:DD:EE:FF",
|
|
32
|
+
pendingName = "OTHER",
|
|
33
|
+
pendingAddress = "aa:bb:cc:dd:ee:ff",
|
|
34
|
+
)
|
|
35
|
+
)
|
|
36
|
+
assertFalse(
|
|
37
|
+
isPendingMentraLivePairingTarget(
|
|
38
|
+
connectedName = "MENTRA_LIVE_BLE_TARGET",
|
|
39
|
+
connectedAddress = "AA:BB:CC:DD:EE:FF",
|
|
40
|
+
pendingName = "MENTRA_LIVE_BLE_TARGET",
|
|
41
|
+
pendingAddress = "11:22:33:44:55:66",
|
|
42
|
+
)
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
@Test
|
|
47
|
+
fun connectedPairingRecoveryRequiresActiveGattConnection() {
|
|
48
|
+
assertFalse(
|
|
49
|
+
shouldRecoverConnectedMentraLivePairingTarget(
|
|
50
|
+
isConnected = false,
|
|
51
|
+
connectedName = "MENTRA_LIVE_BLE_TARGET",
|
|
52
|
+
connectedAddress = "AA:BB:CC:DD:EE:FF",
|
|
53
|
+
pendingName = "MENTRA_LIVE_BLE_TARGET",
|
|
54
|
+
pendingAddress = "AA:BB:CC:DD:EE:FF",
|
|
55
|
+
)
|
|
56
|
+
)
|
|
57
|
+
assertTrue(
|
|
58
|
+
shouldRecoverConnectedMentraLivePairingTarget(
|
|
59
|
+
isConnected = true,
|
|
60
|
+
connectedName = "MENTRA_LIVE_BLE_TARGET",
|
|
61
|
+
connectedAddress = "AA:BB:CC:DD:EE:FF",
|
|
62
|
+
pendingName = "MENTRA_LIVE_BLE_TARGET",
|
|
63
|
+
pendingAddress = "AA:BB:CC:DD:EE:FF",
|
|
64
|
+
)
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
|
|
10
68
|
@Test
|
|
11
69
|
fun parsesMarkedSecurePairingAdvertisement() {
|
|
12
70
|
val result = MentraLivePairingAdvertisementParser.parse(securePayload(pairingFlag = 1))
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
export const BLUETOOTH_SDK_RELEASE_METADATA = Object.freeze({
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"familyBaseVersion": "3.1.0",
|
|
4
|
-
"releaseIdentity": "3.1.0-dev.
|
|
5
|
-
"releaseSetId": "mentra-3.1.0-dev.
|
|
6
|
-
"sourceCommit": "
|
|
7
|
-
"otaManifestUrl": "https://github.com/Mentra-Community/MentraOS/releases/download/mentra-v3.1.0-dev.
|
|
4
|
+
"releaseIdentity": "3.1.0-dev.17",
|
|
5
|
+
"releaseSetId": "mentra-3.1.0-dev.17",
|
|
6
|
+
"sourceCommit": "84c1e737fddc23bf9abb9b1916937c5b7e892f54",
|
|
7
|
+
"otaManifestUrl": "https://github.com/Mentra-Community/MentraOS/releases/download/mentra-v3.1.0-dev.17/mentra-live-ota-3.1.0-dev.17.json",
|
|
8
8
|
"otaManifestSha256": "47e576dec6afd4cc6ccbc758b12fe65b33b180382a899f8098dfcd11f7bcb2a6"
|
|
9
9
|
});
|
|
10
10
|
//# sourceMappingURL=releaseMetadata.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"releaseMetadata.js","sourceRoot":"","sources":["../../src/generated/releaseMetadata.ts"],"names":[],"mappings":"AAWA,MAAM,CAAC,MAAM,8BAA8B,GAA0C,MAAM,CAAC,MAAM,CAAC;IACjG,eAAe,EAAE,CAAC;IAClB,mBAAmB,EAAE,OAAO;IAC5B,iBAAiB,EAAE,cAAc;IACjC,cAAc,EAAE,qBAAqB;IACrC,cAAc,EAAE,0CAA0C;IAC1D,gBAAgB,EAAE,uHAAuH;IACzI,mBAAmB,EAAE,kEAAkE;CACxF,CAAC,CAAA","sourcesContent":["/** Generated by release CI. Do not edit in a release checkout. */\nexport interface BluetoothSdkReleaseMetadata {\n schemaVersion: 1\n familyBaseVersion: string | null\n releaseIdentity: string | null\n releaseSetId: string | null\n sourceCommit: string | null\n otaManifestUrl: string | null\n otaManifestSha256: string | null\n}\n\nexport const BLUETOOTH_SDK_RELEASE_METADATA: Readonly<BluetoothSdkReleaseMetadata> = Object.freeze({\n \"schemaVersion\": 1,\n \"familyBaseVersion\": \"3.1.0\",\n \"releaseIdentity\": \"3.1.0-dev.
|
|
1
|
+
{"version":3,"file":"releaseMetadata.js","sourceRoot":"","sources":["../../src/generated/releaseMetadata.ts"],"names":[],"mappings":"AAWA,MAAM,CAAC,MAAM,8BAA8B,GAA0C,MAAM,CAAC,MAAM,CAAC;IACjG,eAAe,EAAE,CAAC;IAClB,mBAAmB,EAAE,OAAO;IAC5B,iBAAiB,EAAE,cAAc;IACjC,cAAc,EAAE,qBAAqB;IACrC,cAAc,EAAE,0CAA0C;IAC1D,gBAAgB,EAAE,uHAAuH;IACzI,mBAAmB,EAAE,kEAAkE;CACxF,CAAC,CAAA","sourcesContent":["/** Generated by release CI. Do not edit in a release checkout. */\nexport interface BluetoothSdkReleaseMetadata {\n schemaVersion: 1\n familyBaseVersion: string | null\n releaseIdentity: string | null\n releaseSetId: string | null\n sourceCommit: string | null\n otaManifestUrl: string | null\n otaManifestSha256: string | null\n}\n\nexport const BLUETOOTH_SDK_RELEASE_METADATA: Readonly<BluetoothSdkReleaseMetadata> = Object.freeze({\n \"schemaVersion\": 1,\n \"familyBaseVersion\": \"3.1.0\",\n \"releaseIdentity\": \"3.1.0-dev.17\",\n \"releaseSetId\": \"mentra-3.1.0-dev.17\",\n \"sourceCommit\": \"84c1e737fddc23bf9abb9b1916937c5b7e892f54\",\n \"otaManifestUrl\": \"https://github.com/Mentra-Community/MentraOS/releases/download/mentra-v3.1.0-dev.17/mentra-live-ota-3.1.0-dev.17.json\",\n \"otaManifestSha256\": \"47e576dec6afd4cc6ccbc758b12fe65b33b180382a899f8098dfcd11f7bcb2a6\"\n})\n"]}
|
|
@@ -11,7 +11,7 @@ enum BluetoothSdkDefaults {
|
|
|
11
11
|
static let voiceActivityDetectionEnabled = false
|
|
12
12
|
static let loudnessGateEnabled = true
|
|
13
13
|
private static let infoSdkVersionKey = "MentraBluetoothSdkVersion"
|
|
14
|
-
private static let swiftPackageSdkVersion = "3.1.0-dev.
|
|
14
|
+
private static let swiftPackageSdkVersion = "3.1.0-dev.17"
|
|
15
15
|
private static let swiftPackageSdkVersionPlaceholder = "__MENTRA" + "_BLUETOOTH_SDK_VERSION__"
|
|
16
16
|
|
|
17
17
|
private static func normalizedSdkVersion(_ value: String?) -> String? {
|
|
@@ -3,9 +3,9 @@ import Foundation
|
|
|
3
3
|
/// Generated by release CI. Do not edit in a release checkout.
|
|
4
4
|
enum GeneratedReleaseMetadata {
|
|
5
5
|
static let familyBaseVersion = "3.1.0"
|
|
6
|
-
static let releaseIdentity = "3.1.0-dev.
|
|
7
|
-
static let releaseSetId = "mentra-3.1.0-dev.
|
|
8
|
-
static let sourceCommit = "
|
|
9
|
-
static let otaManifestUrl = "https://github.com/Mentra-Community/MentraOS/releases/download/mentra-v3.1.0-dev.
|
|
6
|
+
static let releaseIdentity = "3.1.0-dev.17"
|
|
7
|
+
static let releaseSetId = "mentra-3.1.0-dev.17"
|
|
8
|
+
static let sourceCommit = "84c1e737fddc23bf9abb9b1916937c5b7e892f54"
|
|
9
|
+
static let otaManifestUrl = "https://github.com/Mentra-Community/MentraOS/releases/download/mentra-v3.1.0-dev.17/mentra-live-ota-3.1.0-dev.17.json"
|
|
10
10
|
static let otaManifestSha256 = "47e576dec6afd4cc6ccbc758b12fe65b33b180382a899f8098dfcd11f7bcb2a6"
|
|
11
11
|
}
|
|
@@ -311,6 +311,7 @@ public final class MentraBluetoothSDK {
|
|
|
311
311
|
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "project_name", device.projectName ?? "")
|
|
312
312
|
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "pending_device_name", "")
|
|
313
313
|
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "pending_device_address", "")
|
|
314
|
+
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "pending_device_secure_pairing_capable", "")
|
|
314
315
|
finishDefaultDeviceApply(generation: generation)
|
|
315
316
|
}
|
|
316
317
|
|
|
@@ -324,6 +325,7 @@ public final class MentraBluetoothSDK {
|
|
|
324
325
|
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "project_name", "")
|
|
325
326
|
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "pending_device_name", "")
|
|
326
327
|
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "pending_device_address", "")
|
|
328
|
+
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "pending_device_secure_pairing_capable", "")
|
|
327
329
|
finishDefaultDeviceApply(generation: generation)
|
|
328
330
|
}
|
|
329
331
|
|
|
@@ -401,6 +403,11 @@ public final class MentraBluetoothSDK {
|
|
|
401
403
|
} else if !isController {
|
|
402
404
|
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "pending_device_name", device.name)
|
|
403
405
|
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "pending_device_address", device.identifier ?? "")
|
|
406
|
+
DeviceStore.shared.apply(
|
|
407
|
+
ObservableStore.bluetoothCategory,
|
|
408
|
+
"pending_device_secure_pairing_capable",
|
|
409
|
+
device.securePairingCapable ?? ""
|
|
410
|
+
)
|
|
404
411
|
}
|
|
405
412
|
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "pending_wearable", device.model.deviceType)
|
|
406
413
|
DeviceManager.shared.connectByName(device.name)
|
|
@@ -427,6 +434,7 @@ public final class MentraBluetoothSDK {
|
|
|
427
434
|
clearBluetoothRestoreIntent()
|
|
428
435
|
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "pending_device_name", "")
|
|
429
436
|
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "pending_device_address", "")
|
|
437
|
+
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "pending_device_secure_pairing_capable", "")
|
|
430
438
|
DeviceManager.shared.disconnect()
|
|
431
439
|
}
|
|
432
440
|
|
|
@@ -439,6 +447,7 @@ public final class MentraBluetoothSDK {
|
|
|
439
447
|
clearBluetoothRestoreIntent()
|
|
440
448
|
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "pending_device_name", "")
|
|
441
449
|
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "pending_device_address", "")
|
|
450
|
+
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "pending_device_secure_pairing_capable", "")
|
|
442
451
|
DeviceManager.shared.disconnect()
|
|
443
452
|
}
|
|
444
453
|
|
|
@@ -25,6 +25,35 @@ struct MentraLiveDevice {
|
|
|
25
25
|
let address: String
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
enum MentraLivePendingPairingTarget {
|
|
29
|
+
static func matches(
|
|
30
|
+
connectedName: String,
|
|
31
|
+
connectedIdentifier: String,
|
|
32
|
+
pendingName: String,
|
|
33
|
+
pendingIdentifier: String
|
|
34
|
+
) -> Bool {
|
|
35
|
+
if !pendingIdentifier.isEmpty {
|
|
36
|
+
return connectedIdentifier.caseInsensitiveCompare(pendingIdentifier) == .orderedSame
|
|
37
|
+
}
|
|
38
|
+
return !pendingName.isEmpty && connectedName == pendingName
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
static func shouldRecover(
|
|
42
|
+
isConnected: Bool,
|
|
43
|
+
connectedName: String,
|
|
44
|
+
connectedIdentifier: String,
|
|
45
|
+
pendingName: String,
|
|
46
|
+
pendingIdentifier: String
|
|
47
|
+
) -> Bool {
|
|
48
|
+
isConnected && matches(
|
|
49
|
+
connectedName: connectedName,
|
|
50
|
+
connectedIdentifier: connectedIdentifier,
|
|
51
|
+
pendingName: pendingName,
|
|
52
|
+
pendingIdentifier: pendingIdentifier
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
28
57
|
// MARK: - BlePhotoUploadService
|
|
29
58
|
|
|
30
59
|
class BlePhotoUploadService {
|
|
@@ -967,6 +996,7 @@ extension MentraLive: CBCentralManagerDelegate {
|
|
|
967
996
|
self.isConnecting = false
|
|
968
997
|
self.connectingPeripheral = nil
|
|
969
998
|
self.connectedPeripheral = peripheral
|
|
999
|
+
self.emitConnectedPendingDeviceForPairingScan()
|
|
970
1000
|
|
|
971
1001
|
// Save device name and address for future reconnection
|
|
972
1002
|
if let name = peripheral.name {
|
|
@@ -1618,6 +1648,7 @@ class MentraLive: NSObject, SGCManager {
|
|
|
1618
1648
|
|
|
1619
1649
|
// State Tracking
|
|
1620
1650
|
private var isScanning = false
|
|
1651
|
+
private var manualDiscoveryActive = false
|
|
1621
1652
|
private var isConnecting = false
|
|
1622
1653
|
private var isKilled = false
|
|
1623
1654
|
/// Glasses opened pairing window — stand down without forgetting identity/bonds.
|
|
@@ -1772,7 +1803,9 @@ class MentraLive: NSObject, SGCManager {
|
|
|
1772
1803
|
// clear the saved device name:
|
|
1773
1804
|
UserDefaults.standard.set("", forKey: PREFS_DEVICE_NAME)
|
|
1774
1805
|
|
|
1806
|
+
manualDiscoveryActive = true
|
|
1775
1807
|
startScan()
|
|
1808
|
+
emitConnectedPendingDeviceForPairingScan()
|
|
1776
1809
|
}
|
|
1777
1810
|
}
|
|
1778
1811
|
|
|
@@ -2364,10 +2397,6 @@ class MentraLive: NSObject, SGCManager {
|
|
|
2364
2397
|
|
|
2365
2398
|
centralManager?.scanForPeripherals(withServices: nil, options: scanOptions)
|
|
2366
2399
|
|
|
2367
|
-
// Fresh advertisements refill pairing metadata. Re-emitting the last scan's
|
|
2368
|
-
// cache would keep a unit pairable after it left pairing mode.
|
|
2369
|
-
emitConnectedDeviceForPairingScan()
|
|
2370
|
-
|
|
2371
2400
|
// var dName = DeviceManager.shared.deviceName
|
|
2372
2401
|
// if dName.isEmpty {
|
|
2373
2402
|
// dName = "MENTRA_LIVE"
|
|
@@ -2385,6 +2414,7 @@ class MentraLive: NSObject, SGCManager {
|
|
|
2385
2414
|
}
|
|
2386
2415
|
|
|
2387
2416
|
func stopScan() {
|
|
2417
|
+
manualDiscoveryActive = false
|
|
2388
2418
|
guard isScanning else { return }
|
|
2389
2419
|
|
|
2390
2420
|
centralManager?.stopScan()
|
|
@@ -5502,22 +5532,33 @@ class MentraLive: NSObject, SGCManager {
|
|
|
5502
5532
|
|
|
5503
5533
|
// MARK: - Event Emission
|
|
5504
5534
|
|
|
5505
|
-
///
|
|
5506
|
-
///
|
|
5507
|
-
|
|
5508
|
-
|
|
5509
|
-
|
|
5510
|
-
|
|
5535
|
+
/// A selected pairing target can stop advertising once GATT connects, before readiness
|
|
5536
|
+
/// promotes it to the default device. Re-emit only that explicit pending target; an
|
|
5537
|
+
/// established owner's connected glasses have no pending identity and remain hidden.
|
|
5538
|
+
private func emitConnectedPendingDeviceForPairingScan() {
|
|
5539
|
+
guard manualDiscoveryActive, !pairingYieldActive, let peripheral = connectedPeripheral,
|
|
5540
|
+
let name = peripheral.name,
|
|
5541
|
+
MentraLivePendingPairingTarget.shouldRecover(
|
|
5542
|
+
isConnected: peripheral.state == .connected,
|
|
5543
|
+
connectedName: name,
|
|
5544
|
+
connectedIdentifier: peripheral.identifier.uuidString,
|
|
5545
|
+
pendingName: DeviceStore.shared.get("bluetooth", "pending_device_name") as? String ?? "",
|
|
5546
|
+
pendingIdentifier: DeviceStore.shared.get("bluetooth", "pending_device_address") as? String ?? ""
|
|
5547
|
+
)
|
|
5511
5548
|
else {
|
|
5512
5549
|
return
|
|
5513
5550
|
}
|
|
5514
|
-
|
|
5551
|
+
|
|
5552
|
+
Bridge.log("LIVE: Pairing scan: recovering connected pending target \(name) (\(peripheral.identifier))")
|
|
5515
5553
|
emitDiscoveredDevice(
|
|
5516
5554
|
name,
|
|
5517
5555
|
identifier: peripheral.identifier.uuidString,
|
|
5518
5556
|
pairingMode: true,
|
|
5519
5557
|
pairingCode: nil,
|
|
5520
|
-
securePairingCapable:
|
|
5558
|
+
securePairingCapable: DeviceStore.shared.get(
|
|
5559
|
+
"bluetooth",
|
|
5560
|
+
"pending_device_secure_pairing_capable"
|
|
5561
|
+
) as? Bool
|
|
5521
5562
|
)
|
|
5522
5563
|
}
|
|
5523
5564
|
|
|
@@ -3,6 +3,62 @@ import Foundation
|
|
|
3
3
|
import XCTest
|
|
4
4
|
|
|
5
5
|
final class MentraLivePairingAdvertisementTests: XCTestCase {
|
|
6
|
+
func testConnectedPairingRecoveryRequiresExplicitPendingTarget() {
|
|
7
|
+
XCTAssertFalse(
|
|
8
|
+
MentraLivePendingPairingTarget.matches(
|
|
9
|
+
connectedName: "MENTRA_LIVE_BLE_OWNER",
|
|
10
|
+
connectedIdentifier: "AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE",
|
|
11
|
+
pendingName: "",
|
|
12
|
+
pendingIdentifier: ""
|
|
13
|
+
)
|
|
14
|
+
)
|
|
15
|
+
XCTAssertTrue(
|
|
16
|
+
MentraLivePendingPairingTarget.matches(
|
|
17
|
+
connectedName: "MENTRA_LIVE_BLE_TARGET",
|
|
18
|
+
connectedIdentifier: "AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE",
|
|
19
|
+
pendingName: "MENTRA_LIVE_BLE_TARGET",
|
|
20
|
+
pendingIdentifier: ""
|
|
21
|
+
)
|
|
22
|
+
)
|
|
23
|
+
XCTAssertTrue(
|
|
24
|
+
MentraLivePendingPairingTarget.matches(
|
|
25
|
+
connectedName: "MENTRA_LIVE_BLE_TARGET",
|
|
26
|
+
connectedIdentifier: "AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE",
|
|
27
|
+
pendingName: "OTHER",
|
|
28
|
+
pendingIdentifier: "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
|
|
29
|
+
)
|
|
30
|
+
)
|
|
31
|
+
XCTAssertFalse(
|
|
32
|
+
MentraLivePendingPairingTarget.matches(
|
|
33
|
+
connectedName: "MENTRA_LIVE_BLE_TARGET",
|
|
34
|
+
connectedIdentifier: "AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE",
|
|
35
|
+
pendingName: "MENTRA_LIVE_BLE_TARGET",
|
|
36
|
+
pendingIdentifier: "11111111-2222-3333-4444-555555555555"
|
|
37
|
+
)
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
func testConnectedPairingRecoveryRequiresActiveGattConnection() {
|
|
42
|
+
XCTAssertFalse(
|
|
43
|
+
MentraLivePendingPairingTarget.shouldRecover(
|
|
44
|
+
isConnected: false,
|
|
45
|
+
connectedName: "MENTRA_LIVE_BLE_TARGET",
|
|
46
|
+
connectedIdentifier: "AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE",
|
|
47
|
+
pendingName: "MENTRA_LIVE_BLE_TARGET",
|
|
48
|
+
pendingIdentifier: "AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE"
|
|
49
|
+
)
|
|
50
|
+
)
|
|
51
|
+
XCTAssertTrue(
|
|
52
|
+
MentraLivePendingPairingTarget.shouldRecover(
|
|
53
|
+
isConnected: true,
|
|
54
|
+
connectedName: "MENTRA_LIVE_BLE_TARGET",
|
|
55
|
+
connectedIdentifier: "AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE",
|
|
56
|
+
pendingName: "MENTRA_LIVE_BLE_TARGET",
|
|
57
|
+
pendingIdentifier: "AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE"
|
|
58
|
+
)
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
|
|
6
62
|
func testConnectionCallbackPolicyRejectsPairingYieldAndStaleAttempts() {
|
|
7
63
|
XCTAssertFalse(
|
|
8
64
|
MentraLiveConnectionAttemptPolicy.shouldAcceptDidConnect(
|
package/package.json
CHANGED
|
@@ -12,9 +12,9 @@ export interface BluetoothSdkReleaseMetadata {
|
|
|
12
12
|
export const BLUETOOTH_SDK_RELEASE_METADATA: Readonly<BluetoothSdkReleaseMetadata> = Object.freeze({
|
|
13
13
|
"schemaVersion": 1,
|
|
14
14
|
"familyBaseVersion": "3.1.0",
|
|
15
|
-
"releaseIdentity": "3.1.0-dev.
|
|
16
|
-
"releaseSetId": "mentra-3.1.0-dev.
|
|
17
|
-
"sourceCommit": "
|
|
18
|
-
"otaManifestUrl": "https://github.com/Mentra-Community/MentraOS/releases/download/mentra-v3.1.0-dev.
|
|
15
|
+
"releaseIdentity": "3.1.0-dev.17",
|
|
16
|
+
"releaseSetId": "mentra-3.1.0-dev.17",
|
|
17
|
+
"sourceCommit": "84c1e737fddc23bf9abb9b1916937c5b7e892f54",
|
|
18
|
+
"otaManifestUrl": "https://github.com/Mentra-Community/MentraOS/releases/download/mentra-v3.1.0-dev.17/mentra-live-ota-3.1.0-dev.17.json",
|
|
19
19
|
"otaManifestSha256": "47e576dec6afd4cc6ccbc758b12fe65b33b180382a899f8098dfcd11f7bcb2a6"
|
|
20
20
|
})
|