@mentra/bluetooth-sdk 0.1.20 → 0.1.21-beta.1
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/README.md +27 -12
- package/android/build.gradle +3 -0
- package/android/src/main/AndroidManifest.xml +3 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkAnalytics.kt +34 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkDefaults.kt +1 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +169 -26
- package/android/src/main/java/com/mentra/bluetoothsdk/Bridge.kt +72 -16
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +127 -8
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +4 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +400 -99
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraLocalNetworkModule.kt +333 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/OtaManifest.kt +43 -3
- package/android/src/main/java/com/mentra/bluetoothsdk/camera/CameraModels.kt +18 -2
- package/android/src/main/java/com/mentra/bluetoothsdk/debug/BleTraceLogger.kt +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/services/Foreground.kt +53 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Ar99.java +2653 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G1.kt +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G2.kt +16 -9
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Mach1.kt +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.kt +342 -42
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraNex.kt +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Nimo.kt +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/SGCManager.kt +7 -2
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Simulated.kt +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/ar99/ota/Ar99OtaManager.java +566 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/ar99/ota/OtaByteUtils.java +18 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/ar99/ota/OtaCommandConstants.java +35 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/ar99/ota/OtaCrc32Util.java +13 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/ar99/ota/OtaGattTransport.java +11 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/ar99/ota/OtaProtocol.java +92 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/status/WifiHotspotStatus.kt +19 -3
- package/android/src/main/java/com/mentra/bluetoothsdk/streaming/StreamModels.kt +52 -2
- package/android/src/main/java/com/mentra/bluetoothsdk/tts/TTSTools.kt +115 -47
- package/android/src/main/java/com/mentra/bluetoothsdk/types/DeviceModels.kt +7 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/BleJsonCompact.java +8 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/Constants.kt +2 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/PhoneAudioMonitor.kt +45 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/audio/Ar99OpusPcmDecoder.java +398 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/audio/PcmStreamPlayer.kt +328 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/OtaManifestDowngradeTest.kt +86 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/StreamStatusEventTest.kt +29 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/camera/PhotoRequestTest.kt +45 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/Ar99AdvertisementMatcherTest.java +22 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/utils/BleJsonCompactTest.java +20 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/utils/PhoneAudioMonitorTest.kt +45 -0
- package/build/BluetoothSdk.types.d.ts +100 -22
- package/build/BluetoothSdk.types.d.ts.map +1 -1
- package/build/BluetoothSdk.types.js +2 -1
- package/build/BluetoothSdk.types.js.map +1 -1
- package/build/_internal.d.ts +3 -0
- package/build/_internal.d.ts.map +1 -1
- package/build/_internal.js +3 -0
- package/build/_internal.js.map +1 -1
- package/build/_private/BluetoothSdkModule.d.ts +31 -4
- package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
- package/build/_private/BluetoothSdkModule.js +26 -3
- package/build/_private/BluetoothSdkModule.js.map +1 -1
- package/build/_private/MentraLocalNetworkModule.d.ts +37 -0
- package/build/_private/MentraLocalNetworkModule.d.ts.map +1 -0
- package/build/_private/MentraLocalNetworkModule.js +3 -0
- package/build/_private/MentraLocalNetworkModule.js.map +1 -0
- package/build/_private/cameraRequestPayload.d.ts +1 -1
- package/build/_private/cameraRequestPayload.d.ts.map +1 -1
- package/build/_private/cameraRequestPayload.js +7 -0
- package/build/_private/cameraRequestPayload.js.map +1 -1
- package/build/_private/photoRequestPayload.d.ts.map +1 -1
- package/build/_private/photoRequestPayload.js +10 -0
- package/build/_private/photoRequestPayload.js.map +1 -1
- package/build/_private/sdkOtaManifest.d.ts +20 -0
- package/build/_private/sdkOtaManifest.d.ts.map +1 -0
- package/build/_private/sdkOtaManifest.js +28 -0
- package/build/_private/sdkOtaManifest.js.map +1 -0
- package/build/index.d.ts +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +11 -1
- package/build/index.js.map +1 -1
- package/expo-module.config.json +1 -0
- package/ios/BluetoothSdkModule.swift +113 -2
- package/ios/Source/BluetoothSdkDefaults.swift +2 -1
- package/ios/Source/Bridge.swift +87 -21
- package/ios/Source/DeviceManager.swift +118 -20
- package/ios/Source/DeviceStore.swift +4 -0
- package/ios/Source/MentraBluetoothSDK.swift +326 -33
- package/ios/Source/OtaManifest.swift +47 -4
- package/ios/Source/audio/PcmStreamPlayer.swift +410 -0
- package/ios/Source/camera/CameraModels.swift +26 -6
- package/ios/Source/internal/BleTraceLogger.swift +1 -1
- package/ios/Source/internal/BluetoothSdkAnalytics.swift +39 -0
- package/ios/Source/sgcs/Ar99.swift +2451 -0
- package/ios/Source/sgcs/G1.swift +1 -1
- package/ios/Source/sgcs/G2.swift +19 -7
- package/ios/Source/sgcs/Mach1.swift +1 -1
- package/ios/Source/sgcs/MentraLive.swift +478 -61
- package/ios/Source/sgcs/MentraNex.swift +1 -1
- package/ios/Source/sgcs/Nimo.swift +1 -1
- package/ios/Source/sgcs/SGCManager.swift +13 -2
- package/ios/Source/sgcs/Simulated.swift +1 -1
- package/ios/Source/sgcs/ar99/ota/Ar99OtaManager.swift +637 -0
- package/ios/Source/status/WifiHotspotStatus.swift +21 -2
- package/ios/Source/streaming/StreamModels.swift +68 -2
- package/ios/Source/tts/TTSTools.swift +1 -1
- package/ios/Source/types/DeviceModels.swift +13 -0
- package/ios/Source/utils/BleJsonCompact.swift +8 -0
- package/ios/Source/utils/Constants.swift +2 -0
- package/ios/Source/utils/PhoneAudioMonitor.swift +4 -0
- package/package.json +1 -2
- package/src/BluetoothSdk.types.ts +106 -24
- package/src/_internal.ts +3 -0
- package/src/_private/BluetoothSdkModule.ts +83 -18
- package/src/_private/MentraLocalNetworkModule.ts +50 -0
- package/src/_private/cameraRequestPayload.ts +9 -2
- package/src/_private/photoRequestPayload.ts +13 -4
- package/src/_private/sdkOtaManifest.ts +31 -0
- package/src/index.ts +13 -1
|
@@ -250,7 +250,8 @@ public class Bridge private constructor() {
|
|
|
250
250
|
deviceModel: String,
|
|
251
251
|
deviceName: String,
|
|
252
252
|
deviceAddress: String = "",
|
|
253
|
-
rssi: Int? = null
|
|
253
|
+
rssi: Int? = null,
|
|
254
|
+
projectName: String? = null
|
|
254
255
|
) {
|
|
255
256
|
val searchResults =
|
|
256
257
|
(DeviceStore.store.getCategory("bluetooth")["searchResults"] as? List<*>)
|
|
@@ -262,7 +263,7 @@ public class Bridge private constructor() {
|
|
|
262
263
|
?.toMap()
|
|
263
264
|
}
|
|
264
265
|
?: emptyList()
|
|
265
|
-
val id =
|
|
266
|
+
val id = listOfNotNull(deviceModel, projectName?.takeIf { it.isNotBlank() }, deviceName).joinToString(":")
|
|
266
267
|
val newResult =
|
|
267
268
|
buildMap<String, Any> {
|
|
268
269
|
put("id", id)
|
|
@@ -271,6 +272,7 @@ public class Bridge private constructor() {
|
|
|
271
272
|
if (deviceAddress.isNotBlank()) {
|
|
272
273
|
put("address", deviceAddress)
|
|
273
274
|
}
|
|
275
|
+
projectName?.takeIf { it.isNotBlank() }?.let { put("projectName", it) }
|
|
274
276
|
rssi?.let { put("rssi", it) }
|
|
275
277
|
}
|
|
276
278
|
// Keep the public searchResults array stable as glasses are added or removed.
|
|
@@ -520,30 +522,81 @@ public class Bridge private constructor() {
|
|
|
520
522
|
sendTypedMessage("glasses_serial_number", body as Map<String, Any>)
|
|
521
523
|
}
|
|
522
524
|
|
|
523
|
-
/**
|
|
525
|
+
/**
|
|
526
|
+
* Send WiFi status change. [error] is the glasses' provisioning failure reason
|
|
527
|
+
* (e.g. "connect_timeout", "connected_to_other_network") when this status is the
|
|
528
|
+
* verdict of a failed connect attempt; null for routine link-state updates.
|
|
529
|
+
*/
|
|
524
530
|
@JvmStatic
|
|
525
|
-
|
|
531
|
+
@JvmOverloads
|
|
532
|
+
fun sendWifiStatusChange(
|
|
533
|
+
connected: Boolean,
|
|
534
|
+
ssid: String?,
|
|
535
|
+
localIp: String?,
|
|
536
|
+
error: String? = null
|
|
537
|
+
) {
|
|
526
538
|
val status = WifiStatus.fromStoreFields(connected, ssid, localIp) ?: return
|
|
527
|
-
|
|
539
|
+
val payload =
|
|
540
|
+
if (error != null) status.toMap() + mapOf("error" to error)
|
|
541
|
+
else status.toMap()
|
|
542
|
+
sendTypedMessage("wifi_status_change", payload)
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* Claim the WiFi scan-results store for a newly requested scan. Called by the
|
|
547
|
+
* SDK when it generates the scanId, BEFORE the scan command goes out: store
|
|
548
|
+
* ownership is decided at request time, not by whichever chunk arrives first,
|
|
549
|
+
* so a delayed chunk from an older, abandoned scan can never reset or clobber
|
|
550
|
+
* the current scan's accumulator.
|
|
551
|
+
*/
|
|
552
|
+
@JvmStatic
|
|
553
|
+
fun claimWifiScanResults(scanId: String) {
|
|
554
|
+
DeviceStore.apply("bluetooth", "wifiScanActiveScanId", scanId)
|
|
528
555
|
}
|
|
529
556
|
|
|
530
557
|
/** Send WiFi scan results */
|
|
531
558
|
@JvmStatic
|
|
532
|
-
fun updateWifiScanResults(
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
559
|
+
fun updateWifiScanResults(
|
|
560
|
+
networks: List<Map<String, Any>>,
|
|
561
|
+
scanComplete: Boolean,
|
|
562
|
+
scanId: String? = null
|
|
563
|
+
) {
|
|
564
|
+
// Only chunks echoing the active scanId claimed at request time may mutate
|
|
565
|
+
// the store; foreign chunks are still forwarded to the SDK sink, which
|
|
566
|
+
// drops stale ids itself. Scan-id-less chunks (old firmware) keep the
|
|
567
|
+
// legacy accumulate-forever store behavior.
|
|
568
|
+
val ownsStore =
|
|
569
|
+
scanId == null || scanId == DeviceStore.get("bluetooth", "wifiScanActiveScanId")
|
|
570
|
+
var updatedNetworks: List<Map<String, Any>> = networks
|
|
571
|
+
if (ownsStore) {
|
|
572
|
+
var storedNetworks: List<Map<String, Any>> =
|
|
573
|
+
DeviceStore.get("bluetooth", "wifiScanResults") as? List<Map<String, Any>>
|
|
574
|
+
?: emptyList()
|
|
575
|
+
val lastScanId = DeviceStore.get("bluetooth", "wifiScanResultsScanId")
|
|
576
|
+
if (scanId != null && scanId != lastScanId) {
|
|
577
|
+
// First chunk of a new scan: drop networks accumulated for a previous scan
|
|
578
|
+
// so stale entries never carry over into this scan's store.
|
|
579
|
+
storedNetworks = emptyList()
|
|
580
|
+
DeviceStore.apply("bluetooth", "wifiScanResultsScanId", scanId)
|
|
581
|
+
}
|
|
582
|
+
// add the networks to the storedNetworks array, removing duplicates by ssid
|
|
583
|
+
val merged = storedNetworks.toMutableList()
|
|
584
|
+
for (network in networks) {
|
|
585
|
+
if (!merged.any { it["ssid"] as? String == network["ssid"] as? String }) {
|
|
586
|
+
merged.add(network)
|
|
587
|
+
}
|
|
541
588
|
}
|
|
589
|
+
DeviceStore.apply("bluetooth", "wifiScanResults", merged)
|
|
590
|
+
updatedNetworks = merged
|
|
542
591
|
}
|
|
543
|
-
DeviceStore.apply("bluetooth", "wifiScanResults", updatedNetworks)
|
|
544
592
|
val body = HashMap<String, Any>()
|
|
545
|
-
|
|
593
|
+
// Correlated scans: the SDK accumulates and dedupes chunks per scanId itself,
|
|
594
|
+
// so forward only this chunk; the merged store list is for UI consumers.
|
|
595
|
+
body["networks"] = if (scanId != null) networks else updatedNetworks
|
|
546
596
|
body["scanComplete"] = scanComplete
|
|
597
|
+
if (scanId != null) {
|
|
598
|
+
body["scanId"] = scanId
|
|
599
|
+
}
|
|
547
600
|
sendTypedMessage("wifi_scan_result", body)
|
|
548
601
|
}
|
|
549
602
|
|
|
@@ -821,3 +874,6 @@ public class Bridge private constructor() {
|
|
|
821
874
|
}
|
|
822
875
|
}
|
|
823
876
|
}
|
|
877
|
+
|
|
878
|
+
|
|
879
|
+
|
|
@@ -14,6 +14,7 @@ import com.mentra.bluetoothsdk.controllers.ControllerManager
|
|
|
14
14
|
import com.mentra.bluetoothsdk.controllers.R1
|
|
15
15
|
import com.mentra.bluetoothsdk.services.ForegroundService
|
|
16
16
|
import com.mentra.bluetoothsdk.services.PhoneMic
|
|
17
|
+
import com.mentra.bluetoothsdk.sgcs.Ar99
|
|
17
18
|
import com.mentra.bluetoothsdk.sgcs.G1
|
|
18
19
|
import com.mentra.bluetoothsdk.sgcs.G2
|
|
19
20
|
import com.mentra.bluetoothsdk.sgcs.SceneElement
|
|
@@ -28,6 +29,7 @@ import com.mentra.bluetoothsdk.utils.ControllerTypes
|
|
|
28
29
|
import com.mentra.bluetoothsdk.utils.DeviceTypes
|
|
29
30
|
import com.mentra.bluetoothsdk.utils.MicMap
|
|
30
31
|
import com.mentra.bluetoothsdk.utils.MicTypes
|
|
32
|
+
import com.mentra.bluetoothsdk.utils.PhoneAudioMonitor
|
|
31
33
|
import com.mentra.lc3Lib.Lc3Cpp
|
|
32
34
|
import com.mentra.bluetoothsdk.stt.SherpaOnnxTranscriber
|
|
33
35
|
import kotlinx.coroutines.CoroutineScope
|
|
@@ -329,6 +331,16 @@ class DeviceManager {
|
|
|
329
331
|
return
|
|
330
332
|
}
|
|
331
333
|
|
|
334
|
+
if (sgc?.isMicSuspendedForAudio == true) {
|
|
335
|
+
Bridge.log("MAN: Glasses mic intentionally suspended for phone audio; skipping mic recovery")
|
|
336
|
+
return
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (PhoneAudioMonitor.getInstance(Bridge.getContext()).isOwnAppAudioPlaying()) {
|
|
340
|
+
Bridge.log("MAN: Mentra audio is playing; skipping glasses mic recovery")
|
|
341
|
+
return
|
|
342
|
+
}
|
|
343
|
+
|
|
332
344
|
// When no frame has ever been received, treat elapsed as "forever" so we
|
|
333
345
|
// actually attempt recovery (was 0 before, which made the watchdog a no-op).
|
|
334
346
|
val timeSinceLastLc3Event = System.currentTimeMillis() - (lastLc3Event ?: 0L)
|
|
@@ -571,6 +583,36 @@ class DeviceManager {
|
|
|
571
583
|
}
|
|
572
584
|
}
|
|
573
585
|
|
|
586
|
+
/**
|
|
587
|
+
* Re-evaluate the active foreground-service types while the host Activity is visible.
|
|
588
|
+
*
|
|
589
|
+
* Location is a while-in-use permission, so Android 14+ only allows the service to add
|
|
590
|
+
* the location type from the foreground. Sending a command to the existing service
|
|
591
|
+
* updates its type mask without tearing down the glasses connection.
|
|
592
|
+
*/
|
|
593
|
+
fun refreshForegroundServiceTypes() {
|
|
594
|
+
val context = Bridge.getContext()
|
|
595
|
+
|
|
596
|
+
try {
|
|
597
|
+
Bridge.log("MAN: Refreshing foreground service types")
|
|
598
|
+
val serviceIntent =
|
|
599
|
+
Intent(context, ForegroundService::class.java).apply {
|
|
600
|
+
action = ForegroundService.ACTION_REFRESH_TYPES
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
604
|
+
context.startForegroundService(serviceIntent)
|
|
605
|
+
} else {
|
|
606
|
+
context.startService(serviceIntent)
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
serviceStarted = true
|
|
610
|
+
Bridge.log("MAN: Foreground service types refreshed")
|
|
611
|
+
} catch (e: Exception) {
|
|
612
|
+
Bridge.log("MAN: Failed to refresh foreground service types: ${e.message}")
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
574
616
|
private fun restartForegroundService() {
|
|
575
617
|
val context = Bridge.getContext()
|
|
576
618
|
|
|
@@ -1117,6 +1159,8 @@ class DeviceManager {
|
|
|
1117
1159
|
sgc = MentraLive()
|
|
1118
1160
|
} else if (wearable.contains(DeviceTypes.NEX)) {
|
|
1119
1161
|
sgc = MentraNex()
|
|
1162
|
+
} else if (wearable.contains(DeviceTypes.AR99)) {
|
|
1163
|
+
sgc = Ar99()
|
|
1120
1164
|
} else if (wearable.contains(DeviceTypes.MACH1)) {
|
|
1121
1165
|
sgc = createOptionalMach1Sgc(DeviceTypes.MACH1)
|
|
1122
1166
|
} else if (wearable.contains(DeviceTypes.Z100)) {
|
|
@@ -1194,6 +1238,10 @@ class DeviceManager {
|
|
|
1194
1238
|
}
|
|
1195
1239
|
)
|
|
1196
1240
|
nextTranscriber.initialize()
|
|
1241
|
+
if (!nextTranscriber.isInitialized()) {
|
|
1242
|
+
Bridge.log("SherpaOnnxTranscriber initialize() returned without becoming ready")
|
|
1243
|
+
return false
|
|
1244
|
+
}
|
|
1197
1245
|
transcriber = nextTranscriber
|
|
1198
1246
|
Bridge.log("SherpaOnnxTranscriber fully initialized")
|
|
1199
1247
|
true
|
|
@@ -1280,6 +1328,10 @@ class DeviceManager {
|
|
|
1280
1328
|
Bridge.saveSetting("default_wearable", defaultWearable)
|
|
1281
1329
|
Bridge.saveSetting("device_name", deviceName)
|
|
1282
1330
|
Bridge.saveSetting("device_address", deviceAddress)
|
|
1331
|
+
if (defaultWearable.contains(DeviceTypes.AR99)) {
|
|
1332
|
+
val projectName = (DeviceStore.store.get("bluetooth", "project_name") as? String)?.trim().orEmpty()
|
|
1333
|
+
Bridge.saveSetting("project_name", projectName)
|
|
1334
|
+
}
|
|
1283
1335
|
}
|
|
1284
1336
|
|
|
1285
1337
|
private fun syncSystemTimeOnceForConnection(connectionKey: String) {
|
|
@@ -1556,10 +1608,10 @@ class DeviceManager {
|
|
|
1556
1608
|
sgc?.sendStreamKeepAlive(message)
|
|
1557
1609
|
}
|
|
1558
1610
|
|
|
1559
|
-
fun requestWifiScan() {
|
|
1611
|
+
fun requestWifiScan(scanId: String? = null) {
|
|
1560
1612
|
Bridge.log("MAN: Requesting wifi scan")
|
|
1561
1613
|
DeviceStore.apply("bluetooth", "wifiScanResults", emptyList<Any>())
|
|
1562
|
-
sgc?.requestWifiScan()
|
|
1614
|
+
sgc?.requestWifiScan(scanId)
|
|
1563
1615
|
}
|
|
1564
1616
|
|
|
1565
1617
|
fun sendIncidentId(incidentId: String, apiBaseUrl: String? = null) {
|
|
@@ -1606,6 +1658,20 @@ class DeviceManager {
|
|
|
1606
1658
|
(sgc as? MentraLive)?.sendOtaQueryStatus()
|
|
1607
1659
|
}
|
|
1608
1660
|
|
|
1661
|
+
fun startAr99OtaFromFile(path: String): Boolean {
|
|
1662
|
+
val ar99 = sgc as? Ar99 ?: throw IllegalStateException("unsupported_device")
|
|
1663
|
+
return ar99.startOtaFromFile(path)
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
fun cancelAr99Ota() {
|
|
1667
|
+
(sgc as? Ar99)?.cancelAr99Ota()
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
fun sendAr99FactoryReset() {
|
|
1671
|
+
val ar99 = sgc as? Ar99 ?: throw IllegalStateException("unsupported_device")
|
|
1672
|
+
ar99.sendFactoryReset()
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1609
1675
|
fun sendGalleryMode(requestId: String, enabled: Boolean) {
|
|
1610
1676
|
val live = sgc as? MentraLive ?: throw IllegalStateException("unsupported_device")
|
|
1611
1677
|
live.sendGalleryMode(requestId, enabled)
|
|
@@ -1649,6 +1715,38 @@ class DeviceManager {
|
|
|
1649
1715
|
live.sendCameraFovSetting(requestId, fov, roiPosition)
|
|
1650
1716
|
}
|
|
1651
1717
|
|
|
1718
|
+
/** Sends the pre-lease FOV command used by ASG clients that do not send an acknowledgement. */
|
|
1719
|
+
fun sendLegacyCameraFovSetting(fov: Int, roiPosition: Int) {
|
|
1720
|
+
val glassesConnected = DeviceStore.get("glasses", "connected") as? Boolean ?: false
|
|
1721
|
+
if (!glassesConnected) throw IllegalStateException("not_connected")
|
|
1722
|
+
val live = sgc as? MentraLive ?: throw IllegalStateException("unsupported_device")
|
|
1723
|
+
live.sendCameraFovSetting(null, fov, roiPosition)
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
/** Replays the persistent base FOV without waiting for an acknowledgement. */
|
|
1727
|
+
fun restoreLegacyCameraFovSetting() {
|
|
1728
|
+
val glassesConnected = DeviceStore.get("glasses", "connected") as? Boolean ?: false
|
|
1729
|
+
if (!glassesConnected) throw IllegalStateException("not_connected")
|
|
1730
|
+
val live = sgc as? MentraLive ?: throw IllegalStateException("unsupported_device")
|
|
1731
|
+
live.sendCameraFovSetting()
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1734
|
+
fun sendCameraFovOverride(
|
|
1735
|
+
requestId: String,
|
|
1736
|
+
leaseId: String,
|
|
1737
|
+
fov: Int,
|
|
1738
|
+
roiPosition: Int,
|
|
1739
|
+
ttlMs: Int,
|
|
1740
|
+
) {
|
|
1741
|
+
val live = sgc as? MentraLive ?: throw IllegalStateException("unsupported_device")
|
|
1742
|
+
live.sendCameraFovOverride(requestId, leaseId, fov, roiPosition, ttlMs)
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
fun releaseCameraFovOverride(requestId: String, leaseId: String) {
|
|
1746
|
+
val live = sgc as? MentraLive ?: throw IllegalStateException("unsupported_device")
|
|
1747
|
+
live.releaseCameraFovOverride(requestId, leaseId)
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1652
1750
|
fun sendCameraTuningConfig(requestId: String, anrOn: Boolean, gainOn: Boolean) {
|
|
1653
1751
|
val live = sgc as? MentraLive ?: throw IllegalStateException("unsupported_device")
|
|
1654
1752
|
live.sendCameraTuningConfig(requestId, anrOn, gainOn)
|
|
@@ -1657,8 +1755,11 @@ class DeviceManager {
|
|
|
1657
1755
|
fun warmUpCamera(
|
|
1658
1756
|
requestId: String,
|
|
1659
1757
|
size: PhotoSize,
|
|
1758
|
+
mode: PhotoMode = PhotoMode.PHOTO,
|
|
1660
1759
|
exposureTimeNs: Long?,
|
|
1661
1760
|
durationMs: Int,
|
|
1761
|
+
zsl: Boolean? = null,
|
|
1762
|
+
mfnr: Boolean? = null,
|
|
1662
1763
|
) {
|
|
1663
1764
|
// Fail fast like other camera commands so the SDK promise rejects immediately instead of
|
|
1664
1765
|
// hanging until the request timeout with no camera_status.
|
|
@@ -1668,11 +1769,16 @@ class DeviceManager {
|
|
|
1668
1769
|
"unsupported_device",
|
|
1669
1770
|
"This command requires Mentra Live glasses.",
|
|
1670
1771
|
)
|
|
1671
|
-
live.warmUpCamera(requestId, size, exposureTimeNs, durationMs)
|
|
1772
|
+
live.warmUpCamera(requestId, size, mode, exposureTimeNs, durationMs, zsl, mfnr)
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
fun stopCameraWarmUp(requestId: String) {
|
|
1776
|
+
val live = sgc as? MentraLive ?: throw IllegalStateException("unsupported_device")
|
|
1777
|
+
live.stopCameraWarmUp(requestId)
|
|
1672
1778
|
}
|
|
1673
1779
|
|
|
1674
1780
|
/**
|
|
1675
|
-
* Read glasses media step volume (0
|
|
1781
|
+
* Read glasses media step volume (0—5) via K900 on Mentra Live only. Blocks until response,
|
|
1676
1782
|
* error, or timeout (used from JS AsyncFunction on a worker thread).
|
|
1677
1783
|
*/
|
|
1678
1784
|
fun getGlassesMediaVolumeBlocking(): Map<String, Any> {
|
|
@@ -1698,7 +1804,7 @@ class DeviceManager {
|
|
|
1698
1804
|
return result ?: throw IllegalStateException("glasses_volume_empty")
|
|
1699
1805
|
}
|
|
1700
1806
|
|
|
1701
|
-
/** Set glasses media step volume (0
|
|
1807
|
+
/** Set glasses media step volume (0—5) via K900 on Mentra Live only. */
|
|
1702
1808
|
fun setGlassesMediaVolumeBlocking(level: Int): Map<String, Any> {
|
|
1703
1809
|
val live = sgc as? MentraLive ?: throw IllegalStateException("unsupported_device")
|
|
1704
1810
|
val latch = CountDownLatch(1)
|
|
@@ -1830,8 +1936,14 @@ class DeviceManager {
|
|
|
1830
1936
|
Bridge.log("MAN: No default wearable, returning")
|
|
1831
1937
|
return
|
|
1832
1938
|
}
|
|
1833
|
-
|
|
1834
|
-
|
|
1939
|
+
val reconnectTarget =
|
|
1940
|
+
if (defaultWearable.contains(DeviceTypes.AR99) && deviceAddress.isNotBlank()) {
|
|
1941
|
+
deviceAddress
|
|
1942
|
+
} else {
|
|
1943
|
+
deviceName
|
|
1944
|
+
}
|
|
1945
|
+
if (reconnectTarget.isEmpty()) {
|
|
1946
|
+
Bridge.log("MAN: No reconnect target, returning")
|
|
1835
1947
|
return
|
|
1836
1948
|
}
|
|
1837
1949
|
if (!hasBluetoothPermissions()) {
|
|
@@ -1843,7 +1955,7 @@ class DeviceManager {
|
|
|
1843
1955
|
}
|
|
1844
1956
|
initSGC(defaultWearable)
|
|
1845
1957
|
searching = true
|
|
1846
|
-
sgc?.connectById(
|
|
1958
|
+
sgc?.connectById(reconnectTarget)
|
|
1847
1959
|
connectDefaultController()
|
|
1848
1960
|
}
|
|
1849
1961
|
|
|
@@ -1938,6 +2050,11 @@ class DeviceManager {
|
|
|
1938
2050
|
shouldSendBootingMessage = true // Reset for next first connect
|
|
1939
2051
|
// clear glasses properties:
|
|
1940
2052
|
DeviceStore.apply("glasses", "deviceModel", "")
|
|
2053
|
+
// A manufacturing serial is session-bound. Clear it on every disconnect so a
|
|
2054
|
+
// previously connected pair's serial can never be reported for the next
|
|
2055
|
+
// connection (e.g. switching from G1/Ar99, which populate it from the
|
|
2056
|
+
// advertisement, to a model that never writes it, like G2).
|
|
2057
|
+
DeviceStore.apply("glasses", "serialNumber", "")
|
|
1941
2058
|
DeviceStore.apply("glasses", "fullyBooted", false)
|
|
1942
2059
|
DeviceStore.apply("glasses", "connected", false)
|
|
1943
2060
|
DeviceStore.apply(
|
|
@@ -1976,6 +2093,7 @@ class DeviceManager {
|
|
|
1976
2093
|
Bridge.saveSetting("default_wearable", "")
|
|
1977
2094
|
Bridge.saveSetting("device_name", "")
|
|
1978
2095
|
Bridge.saveSetting("device_address", "")
|
|
2096
|
+
Bridge.saveSetting("project_name", "")
|
|
1979
2097
|
}
|
|
1980
2098
|
|
|
1981
2099
|
fun forgetController() {
|
|
@@ -2046,3 +2164,4 @@ class DeviceManager {
|
|
|
2046
2164
|
}
|
|
2047
2165
|
}
|
|
2048
2166
|
}
|
|
2167
|
+
|
|
@@ -125,6 +125,7 @@ object DeviceStore {
|
|
|
125
125
|
store.set("bluetooth", "contextual_dashboard", true)
|
|
126
126
|
store.set("bluetooth", "gallery_mode", true)
|
|
127
127
|
store.set("bluetooth", "voice_activity_detection_enabled", BluetoothSdkDefaults.VOICE_ACTIVITY_DETECTION_ENABLED)
|
|
128
|
+
store.set("bluetooth", "loudness_gate_enabled", BluetoothSdkDefaults.LOUDNESS_GATE_ENABLED)
|
|
128
129
|
store.set("bluetooth", "screen_disabled", false)
|
|
129
130
|
store.set("bluetooth", "button_photo_size", "max")
|
|
130
131
|
store.set("bluetooth", "button_max_recording_time", 10)
|
|
@@ -268,6 +269,9 @@ object DeviceStore {
|
|
|
268
269
|
"bluetooth" to "voice_activity_detection_enabled" -> {
|
|
269
270
|
DeviceManager.getInstance().sgc?.sendVoiceActivityDetectionSetting()
|
|
270
271
|
}
|
|
272
|
+
"bluetooth" to "loudness_gate_enabled" -> {
|
|
273
|
+
DeviceManager.getInstance().sgc?.sendLoudnessGateSetting()
|
|
274
|
+
}
|
|
271
275
|
"bluetooth" to "nex_lc3_audio_playback" -> {
|
|
272
276
|
(value as? Boolean)?.let { enabled ->
|
|
273
277
|
Bridge.log("DeviceStore: nex_lc3_audio_playback changed to $enabled")
|