@mentra/bluetooth-sdk 0.1.21-beta.3 → 3.1.0-beta.100
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 +67 -25
- package/android/build.gradle +7 -4
- package/android/lc3Lib/build.gradle +18 -4
- package/android/silero/build.gradle +9 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +16 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/Bridge.kt +35 -4
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +216 -23
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +2 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/GeneratedChangelogCatalog.kt +7 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/GeneratedReleaseMetadata.kt +11 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +127 -8
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraLocalNetworkModule.kt +93 -12
- package/android/src/main/java/com/mentra/bluetoothsdk/OtaManifest.kt +5 -7
- package/android/src/main/java/com/mentra/bluetoothsdk/ReleaseChangelog.kt +61 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/ScopedNetworkReadiness.kt +17 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/audio/AudioModels.kt +15 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/events/BluetoothEvents.kt +24 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/net/LocalIpv4.kt +57 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/otaserver/LocalOtaServer.kt +304 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/otaserver/MentraOtaServerModule.kt +120 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/photoreceiver/MentraPhotoReceiverModule.kt +2 -49
- package/android/src/main/java/com/mentra/bluetoothsdk/services/Foreground.kt +61 -30
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/BesOtaHeartbeatGuard.kt +49 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/BesOtaProgressMapper.kt +27 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.kt +1393 -284
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLivePairingAdvertisement.kt +54 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/SGCManager.kt +8 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/status/DeviceStatus.kt +17 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/types/DeviceModels.kt +12 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/BleJsonCompact.java +74 -79
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/K900ProtocolUtils.java +3 -3
- package/android/src/test/java/com/mentra/bluetoothsdk/OtaStartResponsePolicyTest.kt +30 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/ReleaseChangelogTest.kt +13 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/ScopedNetworkReadinessTest.kt +39 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/otaserver/LocalOtaServerTest.kt +82 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/services/ForegroundServiceTypeTest.kt +69 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/BesOtaHeartbeatGuardTest.kt +73 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/BesOtaProgressMapperTest.kt +67 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/MentraLiveGattCharacteristicsTest.kt +39 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/MentraLivePairingAdvertisementParserTest.kt +133 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/utils/BleJsonCompactTest.java +148 -7
- package/build/BluetoothSdk.types.d.ts +84 -2
- package/build/BluetoothSdk.types.d.ts.map +1 -1
- package/build/BluetoothSdk.types.js.map +1 -1
- package/build/_internal.d.ts +2 -0
- package/build/_internal.d.ts.map +1 -1
- package/build/_internal.js +2 -0
- package/build/_internal.js.map +1 -1
- package/build/_private/BluetoothSdkModule.d.ts +4 -1
- package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
- package/build/_private/BluetoothSdkModule.js +1 -1
- package/build/_private/BluetoothSdkModule.js.map +1 -1
- package/build/_private/MentraLocalNetworkModule.d.ts +1 -0
- package/build/_private/MentraLocalNetworkModule.d.ts.map +1 -1
- package/build/_private/MentraLocalNetworkModule.js.map +1 -1
- package/build/_private/sdkOtaManifest.d.ts +3 -12
- package/build/_private/sdkOtaManifest.d.ts.map +1 -1
- package/build/_private/sdkOtaManifest.js +5 -17
- package/build/_private/sdkOtaManifest.js.map +1 -1
- package/build/changelogs.d.ts +7 -0
- package/build/changelogs.d.ts.map +1 -0
- package/build/changelogs.js +44 -0
- package/build/changelogs.js.map +1 -0
- package/build/generated/changelogCatalog.d.ts +6 -0
- package/build/generated/changelogCatalog.d.ts.map +1 -0
- package/build/generated/changelogCatalog.js +8 -0
- package/build/generated/changelogCatalog.js.map +1 -0
- package/build/generated/releaseMetadata.d.ts +12 -0
- package/build/generated/releaseMetadata.d.ts.map +1 -0
- package/build/generated/releaseMetadata.js +10 -0
- package/build/generated/releaseMetadata.js.map +1 -0
- package/build/index.d.ts +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +24 -2
- package/build/index.js.map +1 -1
- package/build/ota-server/MentraOtaServer.types.d.ts +14 -0
- package/build/ota-server/MentraOtaServer.types.d.ts.map +1 -0
- package/build/ota-server/MentraOtaServer.types.js +2 -0
- package/build/ota-server/MentraOtaServer.types.js.map +1 -0
- package/build/ota-server/MentraOtaServerModule.d.ts +21 -0
- package/build/ota-server/MentraOtaServerModule.d.ts.map +1 -0
- package/build/ota-server/MentraOtaServerModule.js +3 -0
- package/build/ota-server/MentraOtaServerModule.js.map +1 -0
- package/build/ota-server/MentraOtaServerModule.web.d.ts +10 -0
- package/build/ota-server/MentraOtaServerModule.web.d.ts.map +1 -0
- package/build/ota-server/MentraOtaServerModule.web.js +12 -0
- package/build/ota-server/MentraOtaServerModule.web.js.map +1 -0
- package/build/ota-server/index.d.ts +3 -0
- package/build/ota-server/index.d.ts.map +1 -0
- package/build/ota-server/index.js +3 -0
- package/build/ota-server/index.js.map +1 -0
- package/build/ota-transport/index.d.ts +67 -0
- package/build/ota-transport/index.d.ts.map +1 -0
- package/build/ota-transport/index.js +58 -0
- package/build/ota-transport/index.js.map +1 -0
- package/expo-module.config.json +3 -2
- package/ios/BackgroundOtaArtifactDownloader.swift +125 -0
- package/ios/BluetoothSdkModule.swift +16 -2
- package/ios/LocalIPv4.swift +150 -0
- package/ios/LocalOtaServer.swift +309 -0
- package/ios/MentraBluetoothSDK.podspec +5 -1
- package/ios/MentraOtaServerModule.swift +147 -0
- package/ios/MentraPhotoReceiverModule.swift +1 -81
- package/ios/Source/BluetoothSdkDefaults.swift +4 -1
- package/ios/Source/Bridge.swift +43 -4
- package/ios/Source/DeviceManager.swift +154 -19
- package/ios/Source/DeviceStore.swift +2 -0
- package/ios/Source/GeneratedChangelogCatalog.swift +6 -0
- package/ios/Source/GeneratedReleaseMetadata.swift +11 -0
- package/ios/Source/MentraBluetoothSDK.swift +91 -6
- package/ios/Source/OtaManifest.swift +5 -7
- package/ios/Source/ReleaseChangelog.swift +65 -0
- package/ios/Source/audio/AudioModels.swift +17 -0
- package/ios/Source/camera/CameraModels.swift +1 -0
- package/ios/Source/events/BluetoothEvents.swift +27 -0
- package/ios/Source/sgcs/MentraLive.swift +512 -78
- package/ios/Source/sgcs/MentraLivePairingAdvertisement.swift +64 -0
- package/ios/Source/sgcs/SGCManager.swift +11 -0
- package/ios/Source/status/DeviceStatus.swift +23 -0
- package/ios/Source/types/DeviceModels.swift +23 -2
- package/ios/Source/utils/BleJsonCompact.swift +73 -71
- package/ios/Tests/MentraLivePairingAdvertisementTests.swift +160 -0
- package/ios/Tests/ReleaseChangelogTests.swift +13 -0
- package/package.json +10 -1
- package/plugin/build/withIos.d.ts +3 -1
- package/plugin/build/withIos.js +22 -10
- package/scripts/public-ota-api.test.mjs +37 -0
- package/scripts/verify-release-package.mjs +34 -0
- package/scripts/write-release-metadata.mjs +154 -0
- package/scripts/write-release-metadata.test.mjs +29 -0
- package/src/BluetoothSdk.types.ts +97 -3
- package/src/_internal.ts +2 -0
- package/src/_private/BluetoothSdkModule.ts +5 -5
- package/src/_private/MentraLocalNetworkModule.ts +1 -1
- package/src/_private/sdkOtaManifest.ts +5 -17
- package/src/changelogs.ts +44 -0
- package/src/generated/changelogCatalog.ts +9 -0
- package/src/generated/releaseMetadata.ts +20 -0
- package/src/index.ts +43 -2
- package/src/ota-server/MentraOtaServer.types.ts +10 -0
- package/src/ota-server/MentraOtaServerModule.ts +23 -0
- package/src/ota-server/MentraOtaServerModule.web.ts +17 -0
- package/src/ota-server/index.ts +2 -0
- package/src/ota-transport/index.ts +116 -0
|
@@ -89,6 +89,14 @@ class DeviceManager {
|
|
|
89
89
|
get() = DeviceStore.store.get("bluetooth", "pending_wearable") as? String ?: ""
|
|
90
90
|
set(value) = DeviceStore.apply("bluetooth", "pending_wearable", value)
|
|
91
91
|
|
|
92
|
+
private var pendingDeviceName: String
|
|
93
|
+
get() = DeviceStore.store.get("bluetooth", "pending_device_name") as? String ?: ""
|
|
94
|
+
set(value) = DeviceStore.apply("bluetooth", "pending_device_name", value)
|
|
95
|
+
|
|
96
|
+
private var pendingDeviceAddress: String
|
|
97
|
+
get() = DeviceStore.store.get("bluetooth", "pending_device_address") as? String ?: ""
|
|
98
|
+
set(value) = DeviceStore.apply("bluetooth", "pending_device_address", value)
|
|
99
|
+
|
|
92
100
|
public var deviceName: String
|
|
93
101
|
get() = DeviceStore.store.get("bluetooth", "device_name") as? String ?: ""
|
|
94
102
|
set(value) = DeviceStore.apply("bluetooth", "device_name", value)
|
|
@@ -205,6 +213,7 @@ class DeviceManager {
|
|
|
205
213
|
private var lastReadyHandledAtMs: Long = 0L
|
|
206
214
|
private var lastReadyHandledKey: String = ""
|
|
207
215
|
private var lastSystemTimeSyncConnectionKey: String = ""
|
|
216
|
+
private var pendingSystemTimeSync: Runnable? = null
|
|
208
217
|
|
|
209
218
|
private var systemMicUnavailable: Boolean
|
|
210
219
|
get() = DeviceStore.store.get("bluetooth", "systemMicUnavailable") as? Boolean ?: false
|
|
@@ -250,6 +259,12 @@ class DeviceManager {
|
|
|
250
259
|
// Audio output format - defaults to LC3 for bandwidth savings
|
|
251
260
|
private var audioOutputFormat: AudioOutputFormat = AudioOutputFormat.LC3
|
|
252
261
|
private var lastLc3Event: Long? = null
|
|
262
|
+
private val micHealthLock = Any()
|
|
263
|
+
private var sequenceGapEvents = 0L
|
|
264
|
+
private var decodeFailures = 0L
|
|
265
|
+
private var lastLc3ReceivedAt: Long? = null
|
|
266
|
+
private var lastPcmProducedAt: Long? = null
|
|
267
|
+
private var lastLc3Sequence: Int? = null
|
|
253
268
|
private var micReinitRunnable: Runnable? = null
|
|
254
269
|
private var systemMicAvailabilityRecheckRunnable: Runnable? = null
|
|
255
270
|
|
|
@@ -742,7 +757,69 @@ class DeviceManager {
|
|
|
742
757
|
* this per uplink packet — otherwise the watchdog keeps re-enabling a working mic.
|
|
743
758
|
*/
|
|
744
759
|
fun reportGlassesAudioActivity() {
|
|
745
|
-
|
|
760
|
+
val now = System.currentTimeMillis()
|
|
761
|
+
lastLc3Event = now
|
|
762
|
+
synchronized(micHealthLock) {
|
|
763
|
+
lastPcmProducedAt = now
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
private fun micHealthSnapshotLocked(): MicHealth =
|
|
768
|
+
MicHealth(
|
|
769
|
+
sequenceGapEvents = sequenceGapEvents,
|
|
770
|
+
decodeFailures = decodeFailures,
|
|
771
|
+
lastLc3ReceivedAt = lastLc3ReceivedAt,
|
|
772
|
+
lastPcmProducedAt = lastPcmProducedAt,
|
|
773
|
+
)
|
|
774
|
+
|
|
775
|
+
private fun resetMicHealth() {
|
|
776
|
+
synchronized(micHealthLock) {
|
|
777
|
+
sequenceGapEvents = 0
|
|
778
|
+
decodeFailures = 0
|
|
779
|
+
lastLc3ReceivedAt = null
|
|
780
|
+
lastPcmProducedAt = null
|
|
781
|
+
lastLc3Sequence = null
|
|
782
|
+
}
|
|
783
|
+
lastLc3Event = null
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
fun resetMicSequenceBaseline() {
|
|
787
|
+
synchronized(micHealthLock) { lastLc3Sequence = null }
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
private fun recordLc3Packet(sequenceNumber: Int?) {
|
|
791
|
+
var gap = false
|
|
792
|
+
val health = synchronized(micHealthLock) {
|
|
793
|
+
val now = System.currentTimeMillis()
|
|
794
|
+
lastLc3ReceivedAt = now
|
|
795
|
+
lastLc3Event = now
|
|
796
|
+
if (sequenceNumber != null) {
|
|
797
|
+
val normalized = sequenceNumber and 0xff
|
|
798
|
+
lastLc3Sequence?.let { previous ->
|
|
799
|
+
val expected = (previous + 1) and 0xff
|
|
800
|
+
if (normalized != expected) {
|
|
801
|
+
gap = true
|
|
802
|
+
sequenceGapEvents += 1
|
|
803
|
+
Bridge.log("MAN: LC3 packet sequence mismatch. Expected: $expected, Got: $normalized")
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
lastLc3Sequence = normalized
|
|
807
|
+
}
|
|
808
|
+
micHealthSnapshotLocked()
|
|
809
|
+
}
|
|
810
|
+
if (gap) Bridge.sendMicHealth(health, "sequence_gap")
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
private fun recordMicDecodeFailure() {
|
|
814
|
+
val health = synchronized(micHealthLock) {
|
|
815
|
+
decodeFailures += 1
|
|
816
|
+
micHealthSnapshotLocked()
|
|
817
|
+
}
|
|
818
|
+
Bridge.sendMicHealth(health, "decode_failure")
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
private fun recordMicPcmProduced() {
|
|
822
|
+
synchronized(micHealthLock) { lastPcmProducedAt = System.currentTimeMillis() }
|
|
746
823
|
}
|
|
747
824
|
|
|
748
825
|
/**
|
|
@@ -750,12 +827,14 @@ class DeviceManager {
|
|
|
750
827
|
* canonical LC3 encoding. Note: frameSize here is for glasses→phone decoding, NOT for
|
|
751
828
|
* phone→cloud encoding.
|
|
752
829
|
*/
|
|
753
|
-
|
|
754
|
-
|
|
830
|
+
@JvmOverloads
|
|
831
|
+
fun handleGlassesMicData(rawLC3Data: ByteArray, frameSize: Int = 40, sequenceNumber: Int? = null) {
|
|
832
|
+
recordLc3Packet(sequenceNumber)
|
|
755
833
|
val pcmData: ByteArray?
|
|
756
834
|
synchronized(lc3Lock) {
|
|
757
835
|
if (lc3DecoderPtr == 0L) {
|
|
758
836
|
Bridge.log("MAN: LC3 decoder not initialized, cannot process glasses audio")
|
|
837
|
+
recordMicDecodeFailure()
|
|
759
838
|
return
|
|
760
839
|
}
|
|
761
840
|
|
|
@@ -764,14 +843,17 @@ class DeviceManager {
|
|
|
764
843
|
pcmData = Lc3Cpp.decodeLC3(lc3DecoderPtr, rawLC3Data, frameSize)
|
|
765
844
|
} catch (e: Exception) {
|
|
766
845
|
Bridge.log("MAN: Failed to decode glasses LC3: ${e.message}")
|
|
846
|
+
recordMicDecodeFailure()
|
|
767
847
|
return
|
|
768
848
|
}
|
|
769
849
|
}
|
|
770
850
|
if (pcmData != null && pcmData.isNotEmpty()) {
|
|
771
851
|
// Re-encode to canonical LC3 via handlePcm (outside lock to avoid deadlock)
|
|
852
|
+
recordMicPcmProduced()
|
|
772
853
|
handlePcm(pcmData)
|
|
773
854
|
} else {
|
|
774
855
|
Bridge.log("MAN: LC3 decode returned empty data")
|
|
856
|
+
recordMicDecodeFailure()
|
|
775
857
|
}
|
|
776
858
|
}
|
|
777
859
|
|
|
@@ -948,15 +1030,20 @@ class DeviceManager {
|
|
|
948
1030
|
|
|
949
1031
|
when (currentViewState.layoutType) {
|
|
950
1032
|
"text_wall" -> {
|
|
951
|
-
sgc?.sendTextWall(currentViewState.text)
|
|
1033
|
+
sgc?.sendTextWall(parsePlaceholders(currentViewState.text))
|
|
952
1034
|
}
|
|
953
1035
|
|
|
954
1036
|
"double_text_wall" -> {
|
|
955
|
-
sgc?.sendDoubleTextWall(
|
|
1037
|
+
sgc?.sendDoubleTextWall(
|
|
1038
|
+
parsePlaceholders(currentViewState.topText),
|
|
1039
|
+
parsePlaceholders(currentViewState.bottomText)
|
|
1040
|
+
)
|
|
956
1041
|
}
|
|
957
1042
|
|
|
958
1043
|
"reference_card" -> {
|
|
959
|
-
|
|
1044
|
+
val title = parsePlaceholders(currentViewState.title)
|
|
1045
|
+
val text = parsePlaceholders(currentViewState.text)
|
|
1046
|
+
sgc?.sendTextWall("$title\n\n$text")
|
|
960
1047
|
}
|
|
961
1048
|
|
|
962
1049
|
"bitmap_view" -> {
|
|
@@ -973,7 +1060,7 @@ class DeviceManager {
|
|
|
973
1060
|
|
|
974
1061
|
"positioned_text" -> {
|
|
975
1062
|
sgc?.sendPositionedText(
|
|
976
|
-
currentViewState.text,
|
|
1063
|
+
parsePlaceholders(currentViewState.text),
|
|
977
1064
|
currentViewState.bmpX ?: 0,
|
|
978
1065
|
currentViewState.bmpY ?: 0,
|
|
979
1066
|
currentViewState.bmpWidth ?: 576,
|
|
@@ -994,6 +1081,8 @@ class DeviceManager {
|
|
|
994
1081
|
}
|
|
995
1082
|
|
|
996
1083
|
private fun parsePlaceholders(text: String): String {
|
|
1084
|
+
if (!text.contains('$')) return text
|
|
1085
|
+
|
|
997
1086
|
val dateFormatter = SimpleDateFormat("M/dd, h:mm", Locale.getDefault())
|
|
998
1087
|
val formattedDate = dateFormatter.format(Date())
|
|
999
1088
|
|
|
@@ -1141,7 +1230,7 @@ class DeviceManager {
|
|
|
1141
1230
|
Bridge.log("MAN: Cleaning up previous sgc type: ${sgc?.type}")
|
|
1142
1231
|
sgc?.cleanup()
|
|
1143
1232
|
sgc = null
|
|
1144
|
-
|
|
1233
|
+
resetSystemTimeSync()
|
|
1145
1234
|
}
|
|
1146
1235
|
|
|
1147
1236
|
if (sgc != null) {
|
|
@@ -1264,6 +1353,14 @@ class DeviceManager {
|
|
|
1264
1353
|
return
|
|
1265
1354
|
}
|
|
1266
1355
|
|
|
1356
|
+
if (pendingDeviceName.isNotEmpty()) {
|
|
1357
|
+
deviceName = pendingDeviceName
|
|
1358
|
+
}
|
|
1359
|
+
if (pendingDeviceAddress.isNotEmpty()) {
|
|
1360
|
+
deviceAddress = pendingDeviceAddress
|
|
1361
|
+
}
|
|
1362
|
+
clearPendingConnection()
|
|
1363
|
+
|
|
1267
1364
|
val readyKey = "${sgc?.type}:${deviceName}"
|
|
1268
1365
|
val now = System.currentTimeMillis()
|
|
1269
1366
|
if (readyKey == lastReadyHandledKey && now - lastReadyHandledAtMs < 2000) {
|
|
@@ -1274,7 +1371,7 @@ class DeviceManager {
|
|
|
1274
1371
|
lastReadyHandledAtMs = now
|
|
1275
1372
|
|
|
1276
1373
|
Bridge.log("MAN: handleDeviceReady() ${sgc?.type}")
|
|
1277
|
-
|
|
1374
|
+
resetMicHealth()
|
|
1278
1375
|
defaultWearable = sgc?.type ?: ""
|
|
1279
1376
|
searching = false
|
|
1280
1377
|
|
|
@@ -1344,9 +1441,25 @@ class DeviceManager {
|
|
|
1344
1441
|
}
|
|
1345
1442
|
|
|
1346
1443
|
lastSystemTimeSyncConnectionKey = connectionKey
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1444
|
+
pendingSystemTimeSync?.let { mainHandler.removeCallbacks(it) }
|
|
1445
|
+
val sync =
|
|
1446
|
+
Runnable {
|
|
1447
|
+
pendingSystemTimeSync = null
|
|
1448
|
+
if (lastSystemTimeSyncConnectionKey != connectionKey || sgc !== activeSgc) {
|
|
1449
|
+
return@Runnable
|
|
1450
|
+
}
|
|
1451
|
+
val timestampMs = System.currentTimeMillis()
|
|
1452
|
+
Bridge.log("MAN: Syncing glasses system time once for connection: $timestampMs")
|
|
1453
|
+
activeSgc.sendSetSystemTime(timestampMs)
|
|
1454
|
+
}
|
|
1455
|
+
pendingSystemTimeSync = sync
|
|
1456
|
+
mainHandler.postDelayed(sync, 3000)
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
private fun resetSystemTimeSync() {
|
|
1460
|
+
pendingSystemTimeSync?.let { mainHandler.removeCallbacks(it) }
|
|
1461
|
+
pendingSystemTimeSync = null
|
|
1462
|
+
lastSystemTimeSyncConnectionKey = ""
|
|
1350
1463
|
}
|
|
1351
1464
|
|
|
1352
1465
|
private fun handleG1Ready() {
|
|
@@ -1361,7 +1474,8 @@ class DeviceManager {
|
|
|
1361
1474
|
|
|
1362
1475
|
fun handleDeviceDisconnected() {
|
|
1363
1476
|
Bridge.log("MAN: Device disconnected")
|
|
1364
|
-
|
|
1477
|
+
resetSystemTimeSync()
|
|
1478
|
+
resetMicHealth()
|
|
1365
1479
|
DeviceStore.apply("glasses", "headUp", false)
|
|
1366
1480
|
DeviceStore.apply(
|
|
1367
1481
|
"glasses",
|
|
@@ -1634,6 +1748,11 @@ class DeviceManager {
|
|
|
1634
1748
|
sgc?.sendHotspotState(enabled)
|
|
1635
1749
|
}
|
|
1636
1750
|
|
|
1751
|
+
fun setWifiAdbState(enabled: Boolean) {
|
|
1752
|
+
Bridge.log("MAN: Setting glasses Wi-Fi ADB state: $enabled")
|
|
1753
|
+
sgc?.sendWifiAdbState(enabled)
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1637
1756
|
fun setSystemTime(timestampMs: Long) {
|
|
1638
1757
|
Bridge.log("MAN: Setting glasses system time: $timestampMs")
|
|
1639
1758
|
sgc?.sendSetSystemTime(timestampMs)
|
|
@@ -1868,6 +1987,11 @@ class DeviceManager {
|
|
|
1868
1987
|
)
|
|
1869
1988
|
}
|
|
1870
1989
|
|
|
1990
|
+
fun queryVideoRecordingStatus(requestId: String) {
|
|
1991
|
+
Bridge.log("MAN: Querying video recording status: requestId=$requestId")
|
|
1992
|
+
sgc?.queryVideoRecordingStatus(requestId)
|
|
1993
|
+
}
|
|
1994
|
+
|
|
1871
1995
|
fun stopVideoRecording(requestId: String, webhookUrl: String?, authToken: String?) {
|
|
1872
1996
|
Bridge.log(
|
|
1873
1997
|
"MAN: onStopVideoRecording: requestId=$requestId, webhook=" +
|
|
@@ -1954,6 +2078,11 @@ class DeviceManager {
|
|
|
1954
2078
|
return
|
|
1955
2079
|
}
|
|
1956
2080
|
initSGC(defaultWearable)
|
|
2081
|
+
val live = sgc as? MentraLive
|
|
2082
|
+
if (live?.isPairingYieldActive() == true) {
|
|
2083
|
+
Bridge.log("MAN: connectDefault skipped — Mentra Live pairing yield active")
|
|
2084
|
+
return
|
|
2085
|
+
}
|
|
1957
2086
|
searching = true
|
|
1958
2087
|
sgc?.connectById(reconnectTarget)
|
|
1959
2088
|
connectDefaultController()
|
|
@@ -2006,13 +2135,13 @@ class DeviceManager {
|
|
|
2006
2135
|
return
|
|
2007
2136
|
}
|
|
2008
2137
|
|
|
2009
|
-
disconnect()
|
|
2138
|
+
disconnect(clearPendingIdentity = false)
|
|
2010
2139
|
Thread.sleep(100)
|
|
2011
2140
|
searching = true
|
|
2012
|
-
|
|
2141
|
+
pendingDeviceName = name
|
|
2013
2142
|
|
|
2014
2143
|
initSGC(pendingWearable)
|
|
2015
|
-
sgc?.connectById(
|
|
2144
|
+
sgc?.connectById(name)
|
|
2016
2145
|
}
|
|
2017
2146
|
|
|
2018
2147
|
fun connectDevice(deviceModel: String, deviceName: String) {
|
|
@@ -2039,11 +2168,12 @@ class DeviceManager {
|
|
|
2039
2168
|
handleDeviceReady()
|
|
2040
2169
|
}
|
|
2041
2170
|
|
|
2042
|
-
fun disconnect() {
|
|
2171
|
+
fun disconnect(clearPendingIdentity: Boolean = true) {
|
|
2043
2172
|
sgc?.clearDisplay()
|
|
2044
2173
|
sgc?.disconnect()
|
|
2045
2174
|
sgc = null // Clear the SGC reference after disconnect
|
|
2046
|
-
|
|
2175
|
+
resetSystemTimeSync()
|
|
2176
|
+
resetMicHealth()
|
|
2047
2177
|
searching = false
|
|
2048
2178
|
micEnabled = false
|
|
2049
2179
|
updateMicState()
|
|
@@ -2054,6 +2184,7 @@ class DeviceManager {
|
|
|
2054
2184
|
// previously connected pair can never be reported for the next connection.
|
|
2055
2185
|
DeviceStore.apply("glasses", "serialNumber", "")
|
|
2056
2186
|
DeviceStore.apply("glasses", "bluetoothMacAddress", "")
|
|
2187
|
+
DeviceStore.apply("glasses", "wifiMacAddress", "")
|
|
2057
2188
|
DeviceStore.apply("glasses", "leftMacAddress", "")
|
|
2058
2189
|
DeviceStore.apply("glasses", "rightMacAddress", "")
|
|
2059
2190
|
DeviceStore.apply("glasses", "macAddress", "")
|
|
@@ -2069,6 +2200,15 @@ class DeviceManager {
|
|
|
2069
2200
|
DeviceStore.apply("glasses", "controllerConnected", false)
|
|
2070
2201
|
controller?.disconnect()
|
|
2071
2202
|
controller = null
|
|
2203
|
+
if (clearPendingIdentity) {
|
|
2204
|
+
clearPendingConnection()
|
|
2205
|
+
}
|
|
2206
|
+
}
|
|
2207
|
+
|
|
2208
|
+
private fun clearPendingConnection() {
|
|
2209
|
+
pendingDeviceName = ""
|
|
2210
|
+
pendingDeviceAddress = ""
|
|
2211
|
+
pendingWearable = ""
|
|
2072
2212
|
}
|
|
2073
2213
|
|
|
2074
2214
|
fun disconnectController() {
|
|
@@ -2082,20 +2222,73 @@ class DeviceManager {
|
|
|
2082
2222
|
fun forget() {
|
|
2083
2223
|
Bridge.log("MAN: Forgetting smart glasses")
|
|
2084
2224
|
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2225
|
+
val live = sgc as? MentraLive
|
|
2226
|
+
if (live != null) {
|
|
2227
|
+
// MentraLive.forget() disconnects GATT then removeBond. Do not refuse
|
|
2228
|
+
// during CTKD bonding — Unpair is an explicit user request to drop the pair.
|
|
2229
|
+
live.forget()
|
|
2230
|
+
// MentraLive.forget() already destroyed the SGC. Clear the manager reference and
|
|
2231
|
+
// session state without calling disconnect() again (that would hit a dead instance
|
|
2232
|
+
// and leave a destroyed MentraLive retained for the next scan).
|
|
2233
|
+
sgc = null
|
|
2234
|
+
resetSystemTimeSync()
|
|
2235
|
+
searching = false
|
|
2236
|
+
micEnabled = false
|
|
2237
|
+
updateMicState()
|
|
2238
|
+
shouldSendBootingMessage = true
|
|
2239
|
+
DeviceStore.apply("glasses", "deviceModel", "")
|
|
2240
|
+
DeviceStore.apply("glasses", "serialNumber", "")
|
|
2241
|
+
DeviceStore.apply("glasses", "bluetoothMacAddress", "")
|
|
2242
|
+
DeviceStore.apply("glasses", "leftMacAddress", "")
|
|
2243
|
+
DeviceStore.apply("glasses", "rightMacAddress", "")
|
|
2244
|
+
DeviceStore.apply("glasses", "macAddress", "")
|
|
2245
|
+
DeviceStore.apply("glasses", "fullyBooted", false)
|
|
2246
|
+
DeviceStore.apply("glasses", "connected", false)
|
|
2247
|
+
DeviceStore.apply(
|
|
2248
|
+
"glasses",
|
|
2249
|
+
"voiceActivityDetectionEnabled",
|
|
2250
|
+
BluetoothSdkDefaults.VOICE_ACTIVITY_DETECTION_ENABLED,
|
|
2251
|
+
)
|
|
2252
|
+
searchingController = false
|
|
2253
|
+
DeviceStore.apply("glasses", "controllerConnected", false)
|
|
2254
|
+
controller?.disconnect()
|
|
2255
|
+
controller = null
|
|
2256
|
+
} else {
|
|
2257
|
+
// Typical abandonAttempt path: disconnect() already destroyed SGC and nulled
|
|
2258
|
+
// this.sgc, so MentraLive.forget() never ran and Classic bonds stayed up —
|
|
2259
|
+
// glasses keep IBRT ACL and stop BLE advertising. Only tear Live bonds when
|
|
2260
|
+
// the selected/pending wearable is Mentra Live — forgetting G1/G2 must not
|
|
2261
|
+
// unpair an unrelated Live unit.
|
|
2262
|
+
val liveTarget =
|
|
2263
|
+
defaultWearable == DeviceTypes.LIVE || pendingWearable == DeviceTypes.LIVE
|
|
2264
|
+
if (liveTarget) {
|
|
2265
|
+
val extraAddress =
|
|
2266
|
+
when {
|
|
2267
|
+
defaultWearable == DeviceTypes.LIVE ->
|
|
2268
|
+
deviceAddress.takeIf { it.isNotEmpty() }
|
|
2269
|
+
pendingWearable == DeviceTypes.LIVE ->
|
|
2270
|
+
pendingDeviceAddress.takeIf { it.isNotEmpty() }
|
|
2271
|
+
else -> null
|
|
2272
|
+
}
|
|
2273
|
+
MentraLive.unbondBondedMentraLiveDevices(
|
|
2274
|
+
Bridge.getContext(),
|
|
2275
|
+
extraAddress,
|
|
2276
|
+
)
|
|
2277
|
+
}
|
|
2278
|
+
sgc?.forget()
|
|
2279
|
+
disconnect()
|
|
2280
|
+
}
|
|
2090
2281
|
|
|
2091
2282
|
// Clear state
|
|
2092
2283
|
defaultWearable = ""
|
|
2093
2284
|
deviceName = ""
|
|
2094
2285
|
deviceAddress = ""
|
|
2286
|
+
clearPendingConnection()
|
|
2095
2287
|
Bridge.saveSetting("default_wearable", "")
|
|
2096
2288
|
Bridge.saveSetting("device_name", "")
|
|
2097
2289
|
Bridge.saveSetting("device_address", "")
|
|
2098
2290
|
Bridge.saveSetting("project_name", "")
|
|
2291
|
+
Bridge.saveSetting("pending_wearable", "")
|
|
2099
2292
|
}
|
|
2100
2293
|
|
|
2101
2294
|
fun forgetController() {
|
|
@@ -65,6 +65,7 @@ object DeviceStore {
|
|
|
65
65
|
store.set("glasses", "connectionState", ConnTypes.DISCONNECTED)
|
|
66
66
|
store.set("glasses", "deviceModel", "")
|
|
67
67
|
store.set("glasses", "firmwareVersion", "")
|
|
68
|
+
store.set("glasses", "hotspotOtaVersion", 0)
|
|
68
69
|
store.set("glasses", "micEnabled", false)
|
|
69
70
|
store.set("glasses", "voiceActivityDetectionEnabled", BluetoothSdkDefaults.VOICE_ACTIVITY_DETECTION_ENABLED)
|
|
70
71
|
store.set("glasses", "bluetoothClassicConnected", false)
|
|
@@ -74,6 +75,7 @@ object DeviceStore {
|
|
|
74
75
|
store.set("glasses", "caseBatteryLevel", -1)
|
|
75
76
|
store.set("glasses", "headUp", false)
|
|
76
77
|
store.set("glasses", "bluetoothMacAddress", "")
|
|
78
|
+
store.set("glasses", "wifiMacAddress", "")
|
|
77
79
|
store.set("glasses", "leftMacAddress", "")
|
|
78
80
|
store.set("glasses", "rightMacAddress", "")
|
|
79
81
|
store.set("glasses", "macAddress", "")
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
package com.mentra.bluetoothsdk
|
|
2
|
+
|
|
3
|
+
/** Generated from /changelogs. Do not edit directly. */
|
|
4
|
+
internal val GENERATED_RELEASE_CHANGELOGS: List<ReleaseChangelog> =
|
|
5
|
+
listOf(
|
|
6
|
+
ReleaseChangelog(version = "3.1.0", markdown = "Software updates are more reliable, with clearer progress and recovery when the glasses restart.\n\n- MentraOS, Mentra Engine, the Bluetooth SDK, and the glasses client now share one coordinated release version.\n- Mentra Live updates can continue across APK, system, and firmware restarts without asking the user to start the same update again.\n- Bluetooth photo capture and transfer diagnostics are more precise and less disruptive to normal glasses traffic."),
|
|
7
|
+
)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
package com.mentra.bluetoothsdk
|
|
2
|
+
|
|
3
|
+
/** Generated by release CI. Do not edit in a release checkout. */
|
|
4
|
+
internal object GeneratedReleaseMetadata {
|
|
5
|
+
const val FAMILY_BASE_VERSION: String = "3.1.0"
|
|
6
|
+
const val RELEASE_IDENTITY: String = "3.1.0-beta.100"
|
|
7
|
+
const val RELEASE_SET_ID: String = "mentra-3.1.0-beta.100"
|
|
8
|
+
const val SOURCE_COMMIT: String = "c7c90f50ce7fd83dadc9a005e152db1d88083e4f"
|
|
9
|
+
const val OTA_MANIFEST_URL: String = "https://github.com/Mentra-Community/MentraOS/releases/download/mentra-builds-v3.1.0/mentra-live-ota-3.1.0-beta.100.json"
|
|
10
|
+
const val OTA_MANIFEST_SHA256: String = "7fdf5b1f75d5d07007e45c929495cd4b6800c810c94e3c60c62bda99d1c61552"
|
|
11
|
+
}
|