@mentra/bluetooth-sdk 0.1.5 → 0.1.7
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 +45 -27
- package/android/build.gradle +2 -9
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +53 -36
- package/android/src/main/java/com/mentra/bluetoothsdk/Bridge.kt +22 -9
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +25 -36
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +9 -4
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +68 -56
- package/android/src/main/java/com/mentra/bluetoothsdk/audio/AudioModels.kt +125 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/camera/CameraModels.kt +209 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/connection/ConnectionModels.kt +78 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/controllers/ControllerManager.kt +4 -3
- package/android/src/main/java/com/mentra/bluetoothsdk/controllers/R1.kt +2 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/events/BluetoothEvents.kt +72 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/internal/MapParsing.kt +132 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/requests/DisplayRequests.kt +39 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G1.java +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G2.kt +2 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Mach1.java +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.java +168 -10
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraNex.kt +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/SGCManager.kt +7 -3
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Simulated.kt +3 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/status/DeviceStatus.kt +607 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/status/RuntimeState.kt +201 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/status/WifiHotspotStatus.kt +173 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/streaming/StreamModels.kt +348 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/stt/SherpaOnnxTranscriber.kt +1 -3
- package/android/src/main/java/com/mentra/bluetoothsdk/types/DeviceModels.kt +75 -0
- package/build/BluetoothSdk.types.d.ts +46 -20
- package/build/BluetoothSdk.types.d.ts.map +1 -1
- package/build/BluetoothSdk.types.js +1 -0
- package/build/BluetoothSdk.types.js.map +1 -1
- package/build/_private/BluetoothSdkModule.d.ts +6 -5
- package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
- package/build/_private/BluetoothSdkModule.js +40 -5
- package/build/_private/BluetoothSdkModule.js.map +1 -1
- package/build/index.d.ts +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +4 -2
- package/build/index.js.map +1 -1
- package/build/react/useMentraBluetooth.d.ts +5 -3
- package/build/react/useMentraBluetooth.d.ts.map +1 -1
- package/build/react/useMentraBluetooth.js +15 -4
- package/build/react/useMentraBluetooth.js.map +1 -1
- package/ios/BluetoothSdkModule.swift +45 -28
- package/ios/MentraBluetoothSDK.podspec +0 -3
- package/ios/Source/Audio/AudioModels.swift +159 -0
- package/ios/Source/Bridge.swift +22 -11
- package/ios/Source/Camera/CameraModels.swift +248 -0
- package/ios/Source/Connection/ScanSession.swift +27 -0
- package/ios/Source/DeviceManager.swift +20 -97
- package/ios/Source/DeviceStore.swift +13 -4
- package/ios/Source/Errors/BluetoothError.swift +19 -0
- package/ios/Source/Events/BluetoothEvents.swift +149 -0
- package/ios/Source/Internal/BluetoothAvailability.swift +58 -0
- package/ios/Source/Internal/ValueParsing.swift +90 -0
- package/ios/Source/MentraBluetoothSDK.swift +50 -2150
- package/ios/Source/Requests/DisplayRequests.swift +58 -0
- package/ios/Source/Status/DeviceStatus.swift +471 -0
- package/ios/Source/Status/RuntimeState.swift +359 -0
- package/ios/Source/Status/WifiHotspotStatus.swift +326 -0
- package/ios/Source/Streaming/StreamModels.swift +436 -0
- package/ios/Source/Types/DeviceModels.swift +134 -0
- package/ios/Source/controllers/ControllerManager.swift +3 -3
- package/ios/Source/controllers/R1.swift +1 -1
- package/ios/Source/sgcs/G1.swift +1 -1
- package/ios/Source/sgcs/G2.swift +99 -39
- package/ios/Source/sgcs/Mach1.swift +1 -1
- package/ios/Source/sgcs/MentraLive.swift +109 -12
- package/ios/Source/sgcs/MentraNex.swift +1 -1
- package/ios/Source/sgcs/SGCManager.swift +11 -3
- package/ios/Source/sgcs/Simulated.swift +2 -2
- package/ios/Source/stt/SherpaOnnxTranscriber.swift +1 -4
- package/package.json +1 -1
- package/src/BluetoothSdk.types.ts +48 -28
- package/src/_private/BluetoothSdkModule.ts +47 -21
- package/src/index.ts +6 -4
- package/src/react/useMentraBluetooth.ts +19 -7
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothModels.kt +0 -1810
- package/android/src/main/java/com/mentra/bluetoothsdk/stt/VadGateSpeechPolicy.kt +0 -221
- package/ios/Packages/VAD/Common/VAD/VADQuality.swift +0 -41
- package/ios/Packages/VAD/Common/VAD/VADState.swift +0 -26
- package/ios/Packages/VAD/Common/VAD/VADStrategy.swift +0 -29
- package/ios/Packages/VAD/Common/VAD/VADType.swift +0 -14
- package/ios/Packages/VAD/Data/Configuration.swift +0 -68
- package/ios/Packages/VAD/Data/FrameSize.swift +0 -39
- package/ios/Packages/VAD/Data/Record.swift +0 -13
- package/ios/Packages/VAD/Data/Result.swift +0 -22
- package/ios/Packages/VAD/Data/SampleRate.swift +0 -48
- package/ios/Packages/VAD/Silero/Model/silero_vad.onnx +0 -0
- package/ios/Packages/VAD/Silero/SileroVAD.swift +0 -284
- package/ios/Packages/VAD/Silero/SileroVADStrategy.swift +0 -64
|
@@ -4,10 +4,10 @@ import android.bluetooth.BluetoothManager
|
|
|
4
4
|
import android.content.Context
|
|
5
5
|
import android.os.Handler
|
|
6
6
|
import android.os.Looper
|
|
7
|
-
import com.mentra.bluetoothsdk.utils.ConnTypes
|
|
8
7
|
import com.mentra.bluetoothsdk.utils.ControllerTypes
|
|
9
8
|
import com.mentra.bluetoothsdk.utils.PhoneAudioMonitor
|
|
10
9
|
import java.util.Collections
|
|
10
|
+
import java.util.concurrent.atomic.AtomicBoolean
|
|
11
11
|
|
|
12
12
|
class MentraBluetoothSdk private constructor(
|
|
13
13
|
context: Context,
|
|
@@ -34,6 +34,7 @@ class MentraBluetoothSdk private constructor(
|
|
|
34
34
|
|
|
35
35
|
companion object {
|
|
36
36
|
private val DEFAULT_DEVICE_KEYS = setOf("default_wearable", "device_name", "device_address")
|
|
37
|
+
private val SCAN_STATE_KEYS = setOf("searching", "searchingController", "searchResults")
|
|
37
38
|
private const val DEFAULT_SCAN_TIMEOUT_MS = 15_000L
|
|
38
39
|
|
|
39
40
|
@JvmStatic
|
|
@@ -58,10 +59,22 @@ class MentraBluetoothSdk private constructor(
|
|
|
58
59
|
listeners.remove(listener)
|
|
59
60
|
}
|
|
60
61
|
|
|
61
|
-
fun
|
|
62
|
+
fun getState(): MentraBluetoothState =
|
|
63
|
+
MentraBluetoothState.from(getRawGlassesStatus(), getRawBluetoothStatus())
|
|
64
|
+
|
|
65
|
+
fun getGlasses(): GlassesRuntimeState =
|
|
66
|
+
getState().glasses
|
|
67
|
+
|
|
68
|
+
fun getSdkState(): PhoneSdkRuntimeState =
|
|
69
|
+
getState().sdk
|
|
70
|
+
|
|
71
|
+
fun getScanState(): BluetoothScanState =
|
|
72
|
+
getState().scan
|
|
73
|
+
|
|
74
|
+
internal fun getRawGlassesStatus(): GlassesStatus =
|
|
62
75
|
GlassesStatus.fromMap(DeviceStore.store.getCategory("glasses"))
|
|
63
76
|
|
|
64
|
-
fun
|
|
77
|
+
internal fun getRawBluetoothStatus(): BluetoothStatus =
|
|
65
78
|
BluetoothStatus.fromMap(DeviceStore.store.getCategory(ObservableStore.BLUETOOTH_CATEGORY))
|
|
66
79
|
|
|
67
80
|
fun getDefaultDevice(): Device? = currentDefaultDevice()
|
|
@@ -144,7 +157,7 @@ class MentraBluetoothSdk private constructor(
|
|
|
144
157
|
val latestResults = mutableListOf<Device>()
|
|
145
158
|
lateinit var timeoutRunnable: Runnable
|
|
146
159
|
lateinit var session: ScanSession
|
|
147
|
-
|
|
160
|
+
val finished = AtomicBoolean(false)
|
|
148
161
|
|
|
149
162
|
fun emitResults(devices: List<Device>) {
|
|
150
163
|
latestResults.clear()
|
|
@@ -154,16 +167,13 @@ class MentraBluetoothSdk private constructor(
|
|
|
154
167
|
|
|
155
168
|
val scanListener =
|
|
156
169
|
object : MentraBluetoothSdkCallback() {
|
|
157
|
-
override fun
|
|
158
|
-
|
|
159
|
-
emitResults(results.filter { it.model == model })
|
|
160
|
-
}
|
|
170
|
+
override fun onScanChanged(scan: BluetoothScanState) {
|
|
171
|
+
emitResults(scan.devices.filter { it.model == model })
|
|
161
172
|
}
|
|
162
|
-
|
|
173
|
+
}
|
|
163
174
|
|
|
164
175
|
fun finish(reason: ScanStopReason) {
|
|
165
|
-
if (finished) return
|
|
166
|
-
finished = true
|
|
176
|
+
if (!finished.compareAndSet(false, true)) return
|
|
167
177
|
removeListener(scanListener)
|
|
168
178
|
mainHandler.removeCallbacks(timeoutRunnable)
|
|
169
179
|
session.markStopped()
|
|
@@ -178,7 +188,7 @@ class MentraBluetoothSdk private constructor(
|
|
|
178
188
|
try {
|
|
179
189
|
emitResults(emptyList())
|
|
180
190
|
startScan(model)
|
|
181
|
-
emitResults(
|
|
191
|
+
emitResults(getRawBluetoothStatus().searchResults.filter { it.model == model })
|
|
182
192
|
mainHandler.postDelayed(timeoutRunnable, normalizedTimeoutMs)
|
|
183
193
|
return session
|
|
184
194
|
} catch (error: Throwable) {
|
|
@@ -298,8 +308,12 @@ class MentraBluetoothSdk private constructor(
|
|
|
298
308
|
DeviceStore.apply(ObservableStore.BLUETOOTH_CATEGORY, "screen_disabled", disabled)
|
|
299
309
|
}
|
|
300
310
|
|
|
301
|
-
fun
|
|
302
|
-
DeviceStore.apply(ObservableStore.BLUETOOTH_CATEGORY, "
|
|
311
|
+
fun setGalleryModeEnabled(enabled: Boolean) {
|
|
312
|
+
DeviceStore.apply(ObservableStore.BLUETOOTH_CATEGORY, "gallery_mode", enabled)
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
fun setVoiceActivityDetectionEnabled(enabled: Boolean) {
|
|
316
|
+
DeviceStore.apply(ObservableStore.BLUETOOTH_CATEGORY, "voice_activity_detection_enabled", enabled)
|
|
303
317
|
}
|
|
304
318
|
|
|
305
319
|
fun setButtonPhotoSettings(size: ButtonPhotoSize) {
|
|
@@ -310,14 +324,14 @@ class MentraBluetoothSdk private constructor(
|
|
|
310
324
|
setButtonPhotoSettings(size = settings.size)
|
|
311
325
|
}
|
|
312
326
|
|
|
313
|
-
fun setButtonVideoRecordingSettings(width: Int, height: Int,
|
|
327
|
+
fun setButtonVideoRecordingSettings(width: Int, height: Int, fps: Int) {
|
|
314
328
|
DeviceStore.apply(ObservableStore.BLUETOOTH_CATEGORY, "button_video_width", width)
|
|
315
329
|
DeviceStore.apply(ObservableStore.BLUETOOTH_CATEGORY, "button_video_height", height)
|
|
316
|
-
DeviceStore.apply(ObservableStore.BLUETOOTH_CATEGORY, "button_video_fps",
|
|
330
|
+
DeviceStore.apply(ObservableStore.BLUETOOTH_CATEGORY, "button_video_fps", fps)
|
|
317
331
|
}
|
|
318
332
|
|
|
319
333
|
fun setButtonVideoRecordingSettings(settings: ButtonVideoRecordingSettings) {
|
|
320
|
-
setButtonVideoRecordingSettings(width = settings.width, height = settings.height,
|
|
334
|
+
setButtonVideoRecordingSettings(width = settings.width, height = settings.height, fps = settings.fps)
|
|
321
335
|
}
|
|
322
336
|
|
|
323
337
|
fun setButtonCameraLed(enabled: Boolean) {
|
|
@@ -332,14 +346,13 @@ class MentraBluetoothSdk private constructor(
|
|
|
332
346
|
DeviceStore.apply(
|
|
333
347
|
ObservableStore.BLUETOOTH_CATEGORY,
|
|
334
348
|
"camera_fov",
|
|
335
|
-
mapOf("fov" to fov.fov, "
|
|
349
|
+
mapOf("fov" to fov.fov, "roi_position" to fov.roiPosition),
|
|
336
350
|
)
|
|
337
351
|
}
|
|
338
352
|
|
|
339
353
|
fun setMicState(
|
|
340
354
|
enabled: Boolean,
|
|
341
355
|
useGlassesMic: Boolean = true,
|
|
342
|
-
bypassVad: Boolean = true,
|
|
343
356
|
sendTranscript: Boolean = false,
|
|
344
357
|
sendLc3Data: Boolean = false,
|
|
345
358
|
) {
|
|
@@ -353,7 +366,6 @@ class MentraBluetoothSdk private constructor(
|
|
|
353
366
|
applyMicState(
|
|
354
367
|
sendPcmData = enabled,
|
|
355
368
|
sendTranscript = enabled && sendTranscript,
|
|
356
|
-
bypassVad = bypassVad,
|
|
357
369
|
sendLc3Data = enabled && sendLc3Data,
|
|
358
370
|
)
|
|
359
371
|
}
|
|
@@ -361,13 +373,11 @@ class MentraBluetoothSdk private constructor(
|
|
|
361
373
|
private fun applyMicState(
|
|
362
374
|
sendPcmData: Boolean,
|
|
363
375
|
sendTranscript: Boolean,
|
|
364
|
-
bypassVad: Boolean,
|
|
365
376
|
sendLc3Data: Boolean,
|
|
366
377
|
) {
|
|
367
378
|
DeviceStore.apply(ObservableStore.BLUETOOTH_CATEGORY, "should_send_pcm", sendPcmData)
|
|
368
379
|
DeviceStore.apply(ObservableStore.BLUETOOTH_CATEGORY, "should_send_lc3", sendLc3Data)
|
|
369
380
|
DeviceStore.apply(ObservableStore.BLUETOOTH_CATEGORY, "should_send_transcript", sendTranscript)
|
|
370
|
-
DeviceStore.apply(ObservableStore.BLUETOOTH_CATEGORY, "bypass_vad", bypassVad)
|
|
371
381
|
deviceManager.setMicState()
|
|
372
382
|
}
|
|
373
383
|
|
|
@@ -404,6 +414,9 @@ class MentraBluetoothSdk private constructor(
|
|
|
404
414
|
}
|
|
405
415
|
|
|
406
416
|
fun requestPhoto(request: PhotoRequest) {
|
|
417
|
+
Bridge.log(
|
|
418
|
+
"NATIVE: PHOTO PIPELINE [3b/6] MentraBluetoothSdk.requestPhoto requestId=${request.requestId} appId=${request.appId}"
|
|
419
|
+
)
|
|
407
420
|
deviceManager.requestPhoto(
|
|
408
421
|
request.requestId,
|
|
409
422
|
request.appId,
|
|
@@ -411,7 +424,9 @@ class MentraBluetoothSdk private constructor(
|
|
|
411
424
|
request.webhookUrl,
|
|
412
425
|
request.authToken,
|
|
413
426
|
request.compress.value,
|
|
427
|
+
request.flash,
|
|
414
428
|
request.sound,
|
|
429
|
+
request.exposureTimeNs,
|
|
415
430
|
)
|
|
416
431
|
}
|
|
417
432
|
|
|
@@ -483,13 +498,22 @@ class MentraBluetoothSdk private constructor(
|
|
|
483
498
|
|
|
484
499
|
private fun dispatchStoreUpdate(category: String, changes: Map<String, Any>) {
|
|
485
500
|
when (ObservableStore.normalizeCategory(category)) {
|
|
486
|
-
"glasses" ->
|
|
501
|
+
"glasses" -> {
|
|
502
|
+
val state = getState()
|
|
487
503
|
dispatchToListeners {
|
|
488
|
-
it.
|
|
504
|
+
it.onStateChanged(state)
|
|
505
|
+
it.onGlassesChanged(state.glasses)
|
|
489
506
|
}
|
|
507
|
+
}
|
|
490
508
|
ObservableStore.BLUETOOTH_CATEGORY -> {
|
|
509
|
+
val state = getState()
|
|
510
|
+
val scanChanged = changes.keys.any { it in SCAN_STATE_KEYS }
|
|
491
511
|
dispatchToListeners {
|
|
492
|
-
it.
|
|
512
|
+
it.onStateChanged(state)
|
|
513
|
+
it.onSdkStateChanged(state.sdk)
|
|
514
|
+
if (scanChanged) {
|
|
515
|
+
it.onScanChanged(state.scan)
|
|
516
|
+
}
|
|
493
517
|
}
|
|
494
518
|
if (!suppressDefaultDeviceEvents && changes.keys.any { it in DEFAULT_DEVICE_KEYS }) {
|
|
495
519
|
dispatchDefaultDeviceChanged()
|
|
@@ -499,37 +523,6 @@ class MentraBluetoothSdk private constructor(
|
|
|
499
523
|
}
|
|
500
524
|
}
|
|
501
525
|
|
|
502
|
-
private fun glassesStatusChanges(changes: Map<String, Any>): Map<String, Any> {
|
|
503
|
-
var merged = changes
|
|
504
|
-
|
|
505
|
-
if (changes.keys.any { it in setOf("wifiConnected", "wifiSsid", "wifiLocalIp") }) {
|
|
506
|
-
merged =
|
|
507
|
-
merged +
|
|
508
|
-
mapOf(
|
|
509
|
-
"wifiConnected" to ((DeviceStore.get("glasses", "wifiConnected") as? Boolean) ?: false),
|
|
510
|
-
"wifiSsid" to ((DeviceStore.get("glasses", "wifiSsid") as? String) ?: ""),
|
|
511
|
-
"wifiLocalIp" to ((DeviceStore.get("glasses", "wifiLocalIp") as? String) ?: ""),
|
|
512
|
-
)
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
if (changes.keys.any { it in setOf("connected", "fullyBooted", "connectionState") }) {
|
|
516
|
-
merged =
|
|
517
|
-
merged +
|
|
518
|
-
mapOf(
|
|
519
|
-
"connected" to ((DeviceStore.get("glasses", "connected") as? Boolean) ?: false),
|
|
520
|
-
"fullyBooted" to ((DeviceStore.get("glasses", "fullyBooted") as? Boolean) ?: false),
|
|
521
|
-
"connectionState" to ((DeviceStore.get("glasses", "connectionState") as? String) ?: ConnTypes.DISCONNECTED),
|
|
522
|
-
)
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
if (changes.containsKey("signalStrengthUpdatedAt") && !changes.containsKey("signalStrength")) {
|
|
526
|
-
val signalStrength = (DeviceStore.get("glasses", "signalStrength") as? Number)?.toInt() ?: -1
|
|
527
|
-
merged = merged + ("signalStrength" to signalStrength)
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
return merged
|
|
531
|
-
}
|
|
532
|
-
|
|
533
526
|
private fun dispatchDefaultDeviceChanged() {
|
|
534
527
|
val defaultDevice = currentDefaultDevice()
|
|
535
528
|
dispatchToListeners { it.onDefaultDeviceChanged(defaultDevice) }
|
|
@@ -617,6 +610,25 @@ class MentraBluetoothSdk private constructor(
|
|
|
617
610
|
}
|
|
618
611
|
}
|
|
619
612
|
"head_up" -> dispatchToListeners { it.onHeadUpChanged(data["up"] as? Boolean ?: false) }
|
|
613
|
+
"voice_activity_detection_status" ->
|
|
614
|
+
dispatchToListeners {
|
|
615
|
+
it.onVoiceActivityDetectionStatus(
|
|
616
|
+
VoiceActivityDetectionStatusEvent(
|
|
617
|
+
voiceActivityDetectionEnabled =
|
|
618
|
+
data["voiceActivityDetectionEnabled"] as? Boolean ?: true,
|
|
619
|
+
values = data,
|
|
620
|
+
)
|
|
621
|
+
)
|
|
622
|
+
}
|
|
623
|
+
"speaking_status" ->
|
|
624
|
+
dispatchToListeners {
|
|
625
|
+
it.onSpeakingStatus(
|
|
626
|
+
SpeakingStatusEvent(
|
|
627
|
+
speaking = data["speaking"] as? Boolean ?: false,
|
|
628
|
+
values = data,
|
|
629
|
+
)
|
|
630
|
+
)
|
|
631
|
+
}
|
|
620
632
|
"battery_status" ->
|
|
621
633
|
dispatchToListeners {
|
|
622
634
|
it.onBatteryStatus(
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
package com.mentra.bluetoothsdk
|
|
2
|
+
|
|
3
|
+
enum class MicPreference(val value: String) {
|
|
4
|
+
AUTO("auto"),
|
|
5
|
+
PHONE("phone"),
|
|
6
|
+
GLASSES("glasses"),
|
|
7
|
+
BLUETOOTH("bluetooth"),
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
data class MicPcmEvent(
|
|
11
|
+
val pcm: ByteArray,
|
|
12
|
+
val sampleRate: Int,
|
|
13
|
+
val bitsPerSample: Int,
|
|
14
|
+
val channels: Int,
|
|
15
|
+
val encoding: String,
|
|
16
|
+
val voiceActivityDetectionEnabled: Boolean,
|
|
17
|
+
) {
|
|
18
|
+
constructor(values: Map<String, Any>) : this(
|
|
19
|
+
pcm = values["pcm"] as? ByteArray ?: ByteArray(0),
|
|
20
|
+
sampleRate = numberValue(values, "sampleRate") ?: SAMPLE_RATE,
|
|
21
|
+
bitsPerSample = numberValue(values, "bitsPerSample") ?: BITS_PER_SAMPLE,
|
|
22
|
+
channels = numberValue(values, "channels") ?: CHANNELS,
|
|
23
|
+
encoding = stringValue(values, "encoding") ?: ENCODING,
|
|
24
|
+
voiceActivityDetectionEnabled = boolValue(values, "voiceActivityDetectionEnabled") ?: true,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
fun toMap(): Map<String, Any> =
|
|
28
|
+
mapOf(
|
|
29
|
+
"type" to "mic_pcm",
|
|
30
|
+
"pcm" to pcm,
|
|
31
|
+
"sampleRate" to sampleRate,
|
|
32
|
+
"bitsPerSample" to bitsPerSample,
|
|
33
|
+
"channels" to channels,
|
|
34
|
+
"encoding" to encoding,
|
|
35
|
+
"voiceActivityDetectionEnabled" to voiceActivityDetectionEnabled,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
companion object {
|
|
39
|
+
const val SAMPLE_RATE = 16_000
|
|
40
|
+
const val BITS_PER_SAMPLE = 16
|
|
41
|
+
const val CHANNELS = 1
|
|
42
|
+
const val ENCODING = "pcm_s16le"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
data class MicLc3Event(
|
|
47
|
+
val lc3: ByteArray,
|
|
48
|
+
val sampleRate: Int,
|
|
49
|
+
val channels: Int,
|
|
50
|
+
val encoding: String,
|
|
51
|
+
val frameDurationMs: Int,
|
|
52
|
+
val frameSizeBytes: Int,
|
|
53
|
+
val bitrate: Int,
|
|
54
|
+
val packetizedFromGlasses: Boolean,
|
|
55
|
+
val voiceActivityDetectionEnabled: Boolean,
|
|
56
|
+
) {
|
|
57
|
+
constructor(values: Map<String, Any>) : this(
|
|
58
|
+
lc3 = values["lc3"] as? ByteArray ?: ByteArray(0),
|
|
59
|
+
sampleRate = numberValue(values, "sampleRate") ?: SAMPLE_RATE,
|
|
60
|
+
channels = numberValue(values, "channels") ?: CHANNELS,
|
|
61
|
+
encoding = stringValue(values, "encoding") ?: ENCODING,
|
|
62
|
+
frameDurationMs = numberValue(values, "frameDurationMs") ?: FRAME_DURATION_MS,
|
|
63
|
+
frameSizeBytes = numberValue(values, "frameSizeBytes") ?: DEFAULT_FRAME_SIZE_BYTES,
|
|
64
|
+
bitrate = numberValue(values, "bitrate") ?: DEFAULT_BITRATE,
|
|
65
|
+
packetizedFromGlasses = boolValue(values, "packetizedFromGlasses") ?: false,
|
|
66
|
+
voiceActivityDetectionEnabled = boolValue(values, "voiceActivityDetectionEnabled") ?: true,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
fun toMap(): Map<String, Any> =
|
|
70
|
+
mapOf(
|
|
71
|
+
"type" to "mic_lc3",
|
|
72
|
+
"lc3" to lc3,
|
|
73
|
+
"sampleRate" to sampleRate,
|
|
74
|
+
"channels" to channels,
|
|
75
|
+
"encoding" to encoding,
|
|
76
|
+
"frameDurationMs" to frameDurationMs,
|
|
77
|
+
"frameSizeBytes" to frameSizeBytes,
|
|
78
|
+
"bitrate" to bitrate,
|
|
79
|
+
"packetizedFromGlasses" to packetizedFromGlasses,
|
|
80
|
+
"voiceActivityDetectionEnabled" to voiceActivityDetectionEnabled,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
companion object {
|
|
84
|
+
const val SAMPLE_RATE = 16_000
|
|
85
|
+
const val CHANNELS = 1
|
|
86
|
+
const val ENCODING = "lc3"
|
|
87
|
+
const val FRAME_DURATION_MS = 10
|
|
88
|
+
const val DEFAULT_FRAME_SIZE_BYTES = 60
|
|
89
|
+
const val DEFAULT_BITRATE = DEFAULT_FRAME_SIZE_BYTES * 8 * (1000 / FRAME_DURATION_MS)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
data class LocalTranscriptionEvent(
|
|
94
|
+
val text: String,
|
|
95
|
+
val isFinal: Boolean,
|
|
96
|
+
val values: Map<String, Any>,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
data class GlassesMediaVolumeGetResult(
|
|
100
|
+
val level: Int?,
|
|
101
|
+
val statusCode: Int?,
|
|
102
|
+
val values: Map<String, Any>,
|
|
103
|
+
) {
|
|
104
|
+
companion object {
|
|
105
|
+
fun fromMap(values: Map<String, Any>): GlassesMediaVolumeGetResult =
|
|
106
|
+
GlassesMediaVolumeGetResult(
|
|
107
|
+
level = numberValue(values, "level"),
|
|
108
|
+
statusCode = (values["statusCode"] as? Number)?.toInt(),
|
|
109
|
+
values = values,
|
|
110
|
+
)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
data class GlassesMediaVolumeSetResult(
|
|
115
|
+
val statusCode: Int?,
|
|
116
|
+
val values: Map<String, Any>,
|
|
117
|
+
) {
|
|
118
|
+
companion object {
|
|
119
|
+
fun fromMap(values: Map<String, Any>): GlassesMediaVolumeSetResult =
|
|
120
|
+
GlassesMediaVolumeSetResult(
|
|
121
|
+
statusCode = (values["statusCode"] as? Number)?.toInt(),
|
|
122
|
+
values = values,
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
package com.mentra.bluetoothsdk
|
|
2
|
+
|
|
3
|
+
enum class PhotoSize(val value: String) {
|
|
4
|
+
SMALL("small"),
|
|
5
|
+
MEDIUM("medium"),
|
|
6
|
+
LARGE("large"),
|
|
7
|
+
FULL("full");
|
|
8
|
+
|
|
9
|
+
companion object {
|
|
10
|
+
@JvmStatic
|
|
11
|
+
fun fromValue(value: String?): PhotoSize =
|
|
12
|
+
values().firstOrNull { it.value == value } ?: MEDIUM
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
enum class ButtonPhotoSize(val value: String) {
|
|
17
|
+
SMALL("small"),
|
|
18
|
+
MEDIUM("medium"),
|
|
19
|
+
LARGE("large");
|
|
20
|
+
|
|
21
|
+
companion object {
|
|
22
|
+
@JvmStatic
|
|
23
|
+
fun fromValue(value: String?): ButtonPhotoSize =
|
|
24
|
+
values().firstOrNull { it.value == value } ?: MEDIUM
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
enum class PhotoCompression(val value: String) {
|
|
29
|
+
NONE("none"),
|
|
30
|
+
MEDIUM("medium"),
|
|
31
|
+
HEAVY("heavy");
|
|
32
|
+
|
|
33
|
+
companion object {
|
|
34
|
+
@JvmStatic
|
|
35
|
+
fun fromValue(value: String?): PhotoCompression =
|
|
36
|
+
values().firstOrNull { it.value == value } ?: NONE
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
data class ButtonPhotoSettings(
|
|
41
|
+
val size: ButtonPhotoSize,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
data class ButtonVideoRecordingSettings(
|
|
45
|
+
val width: Int,
|
|
46
|
+
val height: Int,
|
|
47
|
+
val fps: Int,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
enum class CameraFov(val fov: Int, val roiPosition: Int) {
|
|
51
|
+
STANDARD(118, 0),
|
|
52
|
+
WIDE(118, 0),
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
data class PhotoRequest @JvmOverloads constructor(
|
|
56
|
+
val requestId: String,
|
|
57
|
+
val appId: String,
|
|
58
|
+
val size: PhotoSize,
|
|
59
|
+
val webhookUrl: String,
|
|
60
|
+
val authToken: String? = null,
|
|
61
|
+
val compress: PhotoCompression = PhotoCompression.MEDIUM,
|
|
62
|
+
val flash: Boolean = true,
|
|
63
|
+
val sound: Boolean = true,
|
|
64
|
+
/** Sensor exposure time for this capture only (ns), or null for auto exposure */
|
|
65
|
+
val exposureTimeNs: Double? = null,
|
|
66
|
+
) {
|
|
67
|
+
companion object {
|
|
68
|
+
/** Mirrors iOS `BluetoothSdkModule` defaults for keys omitted from the JS bridge. */
|
|
69
|
+
@JvmStatic
|
|
70
|
+
fun fromMap(values: Map<String, Any>): PhotoRequest {
|
|
71
|
+
val rawExp = values["exposureTimeNs"] ?: values["exposure_time_ns"]
|
|
72
|
+
val exposureTimeNs: Double? =
|
|
73
|
+
when (rawExp) {
|
|
74
|
+
is Number -> rawExp.toDouble().takeIf { it.isFinite() && it > 0 }
|
|
75
|
+
else -> null
|
|
76
|
+
}
|
|
77
|
+
return PhotoRequest(
|
|
78
|
+
requestId = stringValue(values, "requestId", "request_id").orEmpty(),
|
|
79
|
+
appId = stringValue(values, "appId", "app_id").orEmpty(),
|
|
80
|
+
size = PhotoSize.fromValue(stringValue(values, "size") ?: "medium"),
|
|
81
|
+
webhookUrl = stringValue(values, "webhookUrl", "webhook_url").orEmpty(),
|
|
82
|
+
authToken = stringValue(values, "authToken", "auth_token")?.takeIf { it.isNotBlank() },
|
|
83
|
+
compress = PhotoCompression.fromValue(stringValue(values, "compress") ?: "none"),
|
|
84
|
+
flash = boolValue(values, "flash") ?: true,
|
|
85
|
+
sound = boolValue(values, "sound") ?: true,
|
|
86
|
+
exposureTimeNs = exposureTimeNs,
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
enum class RgbLedAction(val value: String) {
|
|
93
|
+
ON("on"),
|
|
94
|
+
OFF("off");
|
|
95
|
+
|
|
96
|
+
companion object {
|
|
97
|
+
@JvmStatic
|
|
98
|
+
fun fromValue(value: String?): RgbLedAction =
|
|
99
|
+
values().firstOrNull { it.value == value } ?: OFF
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
enum class RgbLedColor(val value: String) {
|
|
104
|
+
RED("red"),
|
|
105
|
+
GREEN("green"),
|
|
106
|
+
BLUE("blue"),
|
|
107
|
+
ORANGE("orange"),
|
|
108
|
+
WHITE("white");
|
|
109
|
+
|
|
110
|
+
companion object {
|
|
111
|
+
@JvmStatic
|
|
112
|
+
fun fromValue(value: String?): RgbLedColor? =
|
|
113
|
+
values().firstOrNull { it.value == value }
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
data class RgbLedRequest @JvmOverloads constructor(
|
|
118
|
+
val requestId: String,
|
|
119
|
+
val packageName: String?,
|
|
120
|
+
val action: RgbLedAction,
|
|
121
|
+
val color: RgbLedColor?,
|
|
122
|
+
val onDurationMs: Int,
|
|
123
|
+
val offDurationMs: Int,
|
|
124
|
+
val count: Int,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
data class VideoRecordingRequest(
|
|
128
|
+
val requestId: String,
|
|
129
|
+
val save: Boolean,
|
|
130
|
+
val sound: Boolean,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
data class GalleryStatusEvent(
|
|
134
|
+
val values: Map<String, Any>,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
sealed interface PhotoResponse {
|
|
138
|
+
val state: String
|
|
139
|
+
val requestId: String
|
|
140
|
+
val timestamp: Long
|
|
141
|
+
|
|
142
|
+
fun toMap(): Map<String, Any> =
|
|
143
|
+
when (this) {
|
|
144
|
+
is Success -> mapOf(
|
|
145
|
+
"state" to state,
|
|
146
|
+
"requestId" to requestId,
|
|
147
|
+
"uploadUrl" to uploadUrl,
|
|
148
|
+
"timestamp" to timestamp,
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
is Error -> mutableMapOf<String, Any>(
|
|
152
|
+
"state" to state,
|
|
153
|
+
"requestId" to requestId,
|
|
154
|
+
"timestamp" to timestamp,
|
|
155
|
+
"errorMessage" to errorMessage,
|
|
156
|
+
).apply {
|
|
157
|
+
if (!errorCode.isNullOrBlank()) {
|
|
158
|
+
this["errorCode"] = errorCode
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
fun toEventMap(): Map<String, Any> = toMap() + mapOf("type" to "photo_response")
|
|
164
|
+
|
|
165
|
+
data class Success(
|
|
166
|
+
override val requestId: String,
|
|
167
|
+
val uploadUrl: String,
|
|
168
|
+
override val timestamp: Long,
|
|
169
|
+
) : PhotoResponse {
|
|
170
|
+
override val state: String = "success"
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
data class Error(
|
|
174
|
+
override val requestId: String,
|
|
175
|
+
val errorCode: String?,
|
|
176
|
+
val errorMessage: String,
|
|
177
|
+
override val timestamp: Long,
|
|
178
|
+
) : PhotoResponse {
|
|
179
|
+
override val state: String = "error"
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
companion object {
|
|
183
|
+
fun fromMap(values: Map<String, Any>): PhotoResponse {
|
|
184
|
+
val requestId = stringValue(values, "requestId").orEmpty()
|
|
185
|
+
val timestamp = longValue(values, "timestamp") ?: System.currentTimeMillis()
|
|
186
|
+
val state = stringValue(values, "state")?.lowercase()
|
|
187
|
+
return if (state == "success") {
|
|
188
|
+
val uploadUrl = stringValue(values, "uploadUrl").orEmpty()
|
|
189
|
+
Success(requestId = requestId, uploadUrl = uploadUrl, timestamp = timestamp)
|
|
190
|
+
} else {
|
|
191
|
+
Error(
|
|
192
|
+
requestId = requestId,
|
|
193
|
+
errorCode = stringValue(values, "errorCode"),
|
|
194
|
+
errorMessage = stringValue(values, "errorMessage") ?: "Unknown photo error",
|
|
195
|
+
timestamp = timestamp,
|
|
196
|
+
)
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
data class PhotoResponseEvent(
|
|
203
|
+
val response: PhotoResponse,
|
|
204
|
+
) {
|
|
205
|
+
constructor(values: Map<String, Any>) : this(PhotoResponse.fromMap(values))
|
|
206
|
+
|
|
207
|
+
val requestId: String get() = response.requestId
|
|
208
|
+
val values: Map<String, Any> get() = response.toEventMap()
|
|
209
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
package com.mentra.bluetoothsdk
|
|
2
|
+
|
|
3
|
+
import com.mentra.bluetoothsdk.utils.ConnTypes
|
|
4
|
+
import java.util.concurrent.atomic.AtomicBoolean
|
|
5
|
+
|
|
6
|
+
enum class GlassesConnectionState(val value: String) {
|
|
7
|
+
DISCONNECTED(ConnTypes.DISCONNECTED),
|
|
8
|
+
SCANNING(ConnTypes.SCANNING),
|
|
9
|
+
CONNECTING(ConnTypes.CONNECTING),
|
|
10
|
+
BONDING(ConnTypes.BONDING),
|
|
11
|
+
CONNECTED(ConnTypes.CONNECTED);
|
|
12
|
+
|
|
13
|
+
val isConnected: Boolean
|
|
14
|
+
get() = this == CONNECTED
|
|
15
|
+
|
|
16
|
+
val isBusy: Boolean
|
|
17
|
+
get() = this == SCANNING || this == CONNECTING || this == BONDING
|
|
18
|
+
|
|
19
|
+
internal fun toStatusMap(
|
|
20
|
+
connected: Boolean,
|
|
21
|
+
fullyBooted: Boolean,
|
|
22
|
+
): Map<String, Any> =
|
|
23
|
+
when {
|
|
24
|
+
this == CONNECTED || connected || fullyBooted ->
|
|
25
|
+
mapOf("state" to "connected", "fullyBooted" to fullyBooted)
|
|
26
|
+
this == SCANNING -> mapOf("state" to "scanning")
|
|
27
|
+
this == CONNECTING -> mapOf("state" to "connecting")
|
|
28
|
+
this == BONDING -> mapOf("state" to "bonding")
|
|
29
|
+
else -> mapOf("state" to "disconnected")
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
companion object {
|
|
33
|
+
@JvmStatic
|
|
34
|
+
fun fromValue(value: String?): GlassesConnectionState =
|
|
35
|
+
optionalFromValue(value) ?: DISCONNECTED
|
|
36
|
+
|
|
37
|
+
internal fun optionalFromValue(value: String?): GlassesConnectionState? {
|
|
38
|
+
val normalized = value?.trim()?.takeIf { it.isNotEmpty() } ?: return null
|
|
39
|
+
return values().firstOrNull { it.value.equals(normalized, ignoreCase = true) }
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
data class BluetoothError(
|
|
45
|
+
val code: String,
|
|
46
|
+
val message: String,
|
|
47
|
+
val cause: Throwable? = null,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
enum class ScanStopReason {
|
|
51
|
+
COMPLETED,
|
|
52
|
+
CANCELLED,
|
|
53
|
+
ERROR,
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
interface ScanCallback {
|
|
57
|
+
fun onResults(devices: List<Device>) {}
|
|
58
|
+
fun onComplete(devices: List<Device>) {}
|
|
59
|
+
fun onError(error: BluetoothError) {}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
abstract class MentraBluetoothScanCallback : ScanCallback
|
|
63
|
+
|
|
64
|
+
class ScanSession internal constructor(
|
|
65
|
+
private val stopAction: () -> Unit,
|
|
66
|
+
) {
|
|
67
|
+
private val stopped = AtomicBoolean(false)
|
|
68
|
+
|
|
69
|
+
fun stop() {
|
|
70
|
+
if (stopped.compareAndSet(false, true)) {
|
|
71
|
+
stopAction()
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
internal fun markStopped() {
|
|
76
|
+
stopped.set(true)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -22,7 +22,8 @@ abstract class ControllerManager {
|
|
|
22
22
|
authToken: String?,
|
|
23
23
|
compress: String?,
|
|
24
24
|
flash: Boolean,
|
|
25
|
-
sound: Boolean
|
|
25
|
+
sound: Boolean,
|
|
26
|
+
exposureTimeNs: Long?,
|
|
26
27
|
)
|
|
27
28
|
abstract fun startStream(message: Map<String, Any>)
|
|
28
29
|
abstract fun stopStream()
|
|
@@ -132,8 +133,8 @@ abstract class ControllerManager {
|
|
|
132
133
|
val micEnabled: Boolean
|
|
133
134
|
get() = DeviceStore.get("glasses", "micEnabled") as? Boolean ?: false
|
|
134
135
|
|
|
135
|
-
val
|
|
136
|
-
get() = DeviceStore.get("glasses", "
|
|
136
|
+
val voiceActivityDetectionEnabled: Boolean
|
|
137
|
+
get() = DeviceStore.get("glasses", "voiceActivityDetectionEnabled") as? Boolean ?: true
|
|
137
138
|
|
|
138
139
|
val batteryLevel: Int
|
|
139
140
|
get() = DeviceStore.get("glasses", "batteryLevel") as? Int ?: -1
|