@mentra/bluetooth-sdk 0.1.18 → 0.1.20
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 +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +135 -51
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +120 -17
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +5 -6
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +34 -21
- package/android/src/main/java/com/mentra/bluetoothsdk/OtaManifest.kt +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/camera/CameraModels.kt +13 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G1.kt +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G1TextSanitizer.kt +38 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G2.kt +488 -15
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.kt +551 -159
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLiveL2capChannel.kt +274 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraNex.kt +361 -67
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraosBle.java +7501 -3704
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/SGCManager.kt +134 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/status/DeviceStatus.kt +0 -6
- package/android/src/main/java/com/mentra/bluetoothsdk/types/DeviceModels.kt +2 -3
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/BleJsonCompact.java +493 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/BlePhotoUploadService.java +65 -5
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/BleWireProtocol.java +108 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/IncidentLogBleUploadService.java +4 -3
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/K900LengthCodec.java +115 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/K900ProtocolUtils.java +109 -73
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/MessageChunkReassembler.java +106 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/MessageChunker.java +93 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/NexSGCUtils.kt +111 -11
- package/android/src/test/java/com/mentra/bluetoothsdk/BluetoothSdkExceptionTest.kt +18 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/camera/PhotoRequestTest.kt +23 -9
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/G1TextSanitizerTest.kt +31 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/utils/AvifExifStripperTest.java +17 -1
- package/android/src/test/java/com/mentra/bluetoothsdk/utils/BinaryMessageChunkerTest.java +84 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/utils/BleJsonCompactTest.java +158 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/utils/BlePhotoUploadServiceTest.java +9 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/utils/K900ProtocolUtilsEndiannessTest.java +172 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/utils/K900ProtocolUtilsTest.java +24 -0
- package/build/BluetoothSdk.types.d.ts +37 -1
- package/build/BluetoothSdk.types.d.ts.map +1 -1
- package/build/BluetoothSdk.types.js.map +1 -1
- package/build/_private/BluetoothSdkModule.d.ts +4 -0
- package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
- package/build/_private/BluetoothSdkModule.js.map +1 -1
- package/build/_private/photoRequestPayload.d.ts.map +1 -1
- package/build/_private/photoRequestPayload.js +1 -0
- package/build/_private/photoRequestPayload.js.map +1 -1
- package/build/index.d.ts +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +5 -0
- package/build/index.js.map +1 -1
- package/build/types/index.d.ts +3 -0
- package/build/types/index.d.ts.map +1 -0
- package/build/types/index.js +2 -0
- package/build/types/index.js.map +1 -0
- package/ios/Source/BluetoothSdkDefaults.swift +1 -1
- package/ios/Source/DeviceManager.swift +136 -30
- package/ios/Source/DeviceStore.swift +5 -5
- package/ios/Source/MentraBluetoothSDK.swift +40 -24
- package/ios/Source/OtaManifest.swift +1 -1
- package/ios/Source/camera/CameraModels.swift +17 -3
- package/ios/Source/sgcs/G1.swift +1 -1
- package/ios/Source/sgcs/G2.swift +518 -11
- package/ios/Source/sgcs/MentraLive.swift +443 -42
- package/ios/Source/sgcs/MentraLiveL2capChannel.swift +183 -0
- package/ios/Source/sgcs/MentraNex.swift +416 -32
- package/ios/Source/sgcs/SGCManager.swift +155 -0
- package/ios/Source/sgcs/mentraos_ble.pb.swift +726 -122
- package/ios/Source/status/DeviceStatus.swift +0 -8
- package/ios/Source/utils/BleJsonCompact.swift +395 -0
- package/ios/Source/utils/BleWireProtocol.swift +92 -0
- package/ios/Source/utils/G1Text.swift +30 -1
- package/ios/Source/utils/MessageChunkReassembler.swift +82 -0
- package/ios/Source/utils/MessageChunker.swift +73 -0
- package/package.json +14 -7
- package/src/BluetoothSdk.types.ts +39 -1
- package/src/_private/BluetoothSdkModule.ts +4 -0
- package/src/_private/photoRequestPayload.ts +1 -0
- package/src/index.ts +6 -0
- package/src/types/index.ts +7 -0
|
@@ -67,6 +67,7 @@ class MentraBluetoothSdk private constructor(
|
|
|
67
67
|
private val SCAN_STATE_KEYS = setOf("searching", "searchingController", "searchResults")
|
|
68
68
|
private const val DEFAULT_SCAN_TIMEOUT_MS = 15_000L
|
|
69
69
|
private const val DEFAULT_REQUEST_TIMEOUT_MS = 15_000L
|
|
70
|
+
private const val WIFI_SCAN_TIMEOUT_MS = 20_000L
|
|
70
71
|
private const val VIDEO_UPLOAD_STOP_TIMEOUT_MS = 10 * 60 * 1000L
|
|
71
72
|
private const val STREAM_START_TIMEOUT_MS = 30_000L
|
|
72
73
|
private const val STREAM_STOP_TIMEOUT_MS = 15_000L
|
|
@@ -476,6 +477,12 @@ class MentraBluetoothSdk private constructor(
|
|
|
476
477
|
DeviceStore.apply(ObservableStore.BLUETOOTH_CATEGORY, "voice_activity_detection_enabled", enabled)
|
|
477
478
|
}
|
|
478
479
|
|
|
480
|
+
@Deprecated(
|
|
481
|
+
message =
|
|
482
|
+
"Sticky action-button photo presets are deprecated. Prefer per-request " +
|
|
483
|
+
"requestPhoto(...) options (e.g. mode=TEXT for AE ÷3, or explicit per-shot fields). " +
|
|
484
|
+
"This method still works but will be removed in a future release.",
|
|
485
|
+
)
|
|
479
486
|
fun setPhotoCaptureDefaults(settings: PhotoCaptureDefaults): SettingsAckEvent =
|
|
480
487
|
performSettingsCommand(
|
|
481
488
|
setting = "button_photo",
|
|
@@ -533,6 +540,12 @@ class MentraBluetoothSdk private constructor(
|
|
|
533
540
|
performSettingsCommand(
|
|
534
541
|
setting = "button_video_recording",
|
|
535
542
|
updateStore = { _ ->
|
|
543
|
+
DeviceStore.set(
|
|
544
|
+
ObservableStore.BLUETOOTH_CATEGORY,
|
|
545
|
+
"button_video_settings",
|
|
546
|
+
mapOf("width" to defaults.width, "height" to defaults.height, "fps" to defaults.fps),
|
|
547
|
+
)
|
|
548
|
+
// Keep legacy cache keys readable for older internal callers during migration.
|
|
536
549
|
DeviceStore.set(ObservableStore.BLUETOOTH_CATEGORY, "button_video_width", defaults.width)
|
|
537
550
|
DeviceStore.set(ObservableStore.BLUETOOTH_CATEGORY, "button_video_height", defaults.height)
|
|
538
551
|
DeviceStore.set(ObservableStore.BLUETOOTH_CATEGORY, "button_video_fps", defaults.fps)
|
|
@@ -633,35 +646,35 @@ class MentraBluetoothSdk private constructor(
|
|
|
633
646
|
fun requestWifiScan(): List<WifiScanResult> {
|
|
634
647
|
val pending = PendingResponse<List<WifiScanResult>>("WiFi scan request")
|
|
635
648
|
val request = PendingWifiScan(pending)
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
649
|
+
val existing =
|
|
650
|
+
synchronized(oneShotLock) {
|
|
651
|
+
pendingWifiScan ?: run {
|
|
652
|
+
pendingWifiScan = request
|
|
653
|
+
null
|
|
654
|
+
}
|
|
642
655
|
}
|
|
643
|
-
|
|
656
|
+
if (existing != null) {
|
|
657
|
+
// Join the in-flight scan instead of failing with request_in_flight;
|
|
658
|
+
// the scan screen auto-starts a scan on mount and can be pushed twice.
|
|
659
|
+
return existing.pending.await(WIFI_SCAN_TIMEOUT_MS)
|
|
644
660
|
}
|
|
645
661
|
try {
|
|
646
662
|
deviceManager.requestWifiScan()
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
if (pendingWifiScan === request) {
|
|
654
|
-
pendingWifiScan = null
|
|
655
|
-
}
|
|
656
|
-
request.latestResults
|
|
657
|
-
} else {
|
|
658
|
-
emptyList()
|
|
659
|
-
}
|
|
660
|
-
}
|
|
663
|
+
// The glasses wait up to 15s for scan-results broadcasts before sending
|
|
664
|
+
// scan_complete, so give them longer than that before falling back.
|
|
665
|
+
return pending.await(WIFI_SCAN_TIMEOUT_MS)
|
|
666
|
+
} catch (error: Throwable) {
|
|
667
|
+
if (error is BluetoothSdkException && error.code == "request_timeout") {
|
|
668
|
+
val fallbackResults = synchronized(oneShotLock) { request.latestResults }
|
|
661
669
|
if (fallbackResults.isNotEmpty()) {
|
|
670
|
+
// Resolve so callers joined on the same scan get the fallback too.
|
|
671
|
+
pending.resolve(fallbackResults)
|
|
662
672
|
return fallbackResults
|
|
663
673
|
}
|
|
664
674
|
}
|
|
675
|
+
// Fail joined callers immediately instead of letting them run out their
|
|
676
|
+
// own timeout.
|
|
677
|
+
pending.reject(error)
|
|
665
678
|
throw error
|
|
666
679
|
} finally {
|
|
667
680
|
synchronized(oneShotLock) {
|
|
@@ -57,7 +57,7 @@ internal object OtaManifestChecker {
|
|
|
57
57
|
return try {
|
|
58
58
|
val status = connection.responseCode
|
|
59
59
|
if (status !in 200..299) {
|
|
60
|
-
throw BluetoothSdkException("ota_manifest_request_failed", "OTA manifest request failed with HTTP $status.")
|
|
60
|
+
throw BluetoothSdkException("ota_manifest_request_failed", "OTA manifest request failed with HTTP $status for $otaVersionUrl.")
|
|
61
61
|
}
|
|
62
62
|
JSONObject(connection.inputStream.bufferedReader().use { it.readText() })
|
|
63
63
|
} finally {
|
|
@@ -167,6 +167,17 @@ data class CameraFovResult(
|
|
|
167
167
|
}
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
+
enum class PhotoMode(val value: String) {
|
|
171
|
+
PHOTO("photo"),
|
|
172
|
+
TEXT("text");
|
|
173
|
+
|
|
174
|
+
companion object {
|
|
175
|
+
@JvmStatic
|
|
176
|
+
fun fromValue(value: String?): PhotoMode =
|
|
177
|
+
values().firstOrNull { it.value == value } ?: PHOTO
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
170
181
|
data class PhotoRequest @JvmOverloads constructor(
|
|
171
182
|
val requestId: String = generatedCameraRequestId("photo"),
|
|
172
183
|
val size: PhotoSize,
|
|
@@ -188,6 +199,7 @@ data class PhotoRequest @JvmOverloads constructor(
|
|
|
188
199
|
val ispDigitalGain: Int? = null,
|
|
189
200
|
val ispAnalogGain: String? = null,
|
|
190
201
|
val resetCaptureTuning: Boolean? = null,
|
|
202
|
+
val mode: PhotoMode = PhotoMode.PHOTO,
|
|
191
203
|
) {
|
|
192
204
|
companion object {
|
|
193
205
|
/** Mirrors iOS `BluetoothSdkModule` defaults for keys omitted from the JS bridge. */
|
|
@@ -225,6 +237,7 @@ data class PhotoRequest @JvmOverloads constructor(
|
|
|
225
237
|
compress = PhotoCompression.fromValue(stringValue(values, "compress") ?: "none"),
|
|
226
238
|
save = boolValue(values, "save", "saveToGallery") ?: false,
|
|
227
239
|
sound = boolValue(values, "sound") ?: true,
|
|
240
|
+
mode = PhotoMode.fromValue(stringValue(values, "mode")),
|
|
228
241
|
exposureTimeNs = exposureTimeNs,
|
|
229
242
|
iso = iso,
|
|
230
243
|
aeExposureDivisor = aeDivisor,
|
|
@@ -2841,7 +2841,7 @@ class G1 : SGCManager() {
|
|
|
2841
2841
|
private fun chunkTextForTransmission(text: String?): List<ByteArray> {
|
|
2842
2842
|
// Handle empty or whitespace-only text by sending at least a space
|
|
2843
2843
|
// This ensures the display gets updated/cleared properly
|
|
2844
|
-
val textToSend = if (text == null || text.trim().isEmpty()) " " else text
|
|
2844
|
+
val textToSend = if (text == null || text.trim().isEmpty()) " " else sanitizeG1DisplayText(text)
|
|
2845
2845
|
val textBytes = textToSend.toByteArray(StandardCharsets.UTF_8)
|
|
2846
2846
|
val totalChunks = Math.ceil(textBytes.size.toDouble() / MAX_CHUNK_SIZE).toInt()
|
|
2847
2847
|
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
package com.mentra.bluetoothsdk.sgcs
|
|
2
|
+
|
|
3
|
+
import java.text.Normalizer
|
|
4
|
+
|
|
5
|
+
private val G1_COMBINING_MARKS = Regex("\\p{M}+")
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Converts text to the base Latin glyphs available in the Even Realities G1 firmware.
|
|
9
|
+
*
|
|
10
|
+
* G1 does not ship glyphs for combining diacritics. Normalize at the device boundary so
|
|
11
|
+
* miniapps can retain the real text everywhere else and newer glasses receive it unchanged.
|
|
12
|
+
*/
|
|
13
|
+
internal fun sanitizeG1DisplayText(text: String): String {
|
|
14
|
+
val expanded = buildString(text.length) {
|
|
15
|
+
text.forEach { character ->
|
|
16
|
+
when (character) {
|
|
17
|
+
'Đ', 'Ð' -> append('D')
|
|
18
|
+
'đ', 'ð' -> append('d')
|
|
19
|
+
'Ł' -> append('L')
|
|
20
|
+
'ł' -> append('l')
|
|
21
|
+
'Ø' -> append('O')
|
|
22
|
+
'ø' -> append('o')
|
|
23
|
+
'Æ' -> append("AE")
|
|
24
|
+
'æ' -> append("ae")
|
|
25
|
+
'Œ' -> append("OE")
|
|
26
|
+
'œ' -> append("oe")
|
|
27
|
+
'ẞ' -> append("SS")
|
|
28
|
+
'ß' -> append("ss")
|
|
29
|
+
'Þ' -> append("TH")
|
|
30
|
+
'þ' -> append("th")
|
|
31
|
+
else -> append(character)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return Normalizer.normalize(expanded, Normalizer.Form.NFD)
|
|
37
|
+
.replace(G1_COMBINING_MARKS, "")
|
|
38
|
+
}
|