@mentra/bluetooth-sdk 0.1.10 → 0.1.11

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.
Files changed (36) hide show
  1. package/README.md +1 -1
  2. package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +39 -16
  3. package/android/src/main/java/com/mentra/bluetoothsdk/Bridge.kt +14 -0
  4. package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +22 -3
  5. package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +8 -0
  6. package/android/src/main/java/com/mentra/bluetoothsdk/camera/CameraModels.kt +2 -1
  7. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G2.kt +829 -643
  8. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraNex.kt +40 -64
  9. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/SGCManager.kt +11 -1
  10. package/build/BluetoothSdk.types.d.ts +12 -1
  11. package/build/BluetoothSdk.types.d.ts.map +1 -1
  12. package/build/BluetoothSdk.types.js.map +1 -1
  13. package/build/_private/BluetoothSdkModule.d.ts +1 -0
  14. package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
  15. package/build/_private/BluetoothSdkModule.js +14 -1
  16. package/build/_private/BluetoothSdkModule.js.map +1 -1
  17. package/build/index.d.ts +1 -1
  18. package/build/index.d.ts.map +1 -1
  19. package/build/index.js +2 -0
  20. package/build/index.js.map +1 -1
  21. package/ios/BluetoothSdkModule.swift +3 -0
  22. package/ios/Source/Bridge.swift +10 -0
  23. package/ios/Source/DeviceManager.swift +24 -21
  24. package/ios/Source/DeviceStore.swift +8 -2
  25. package/ios/Source/camera/CameraModels.swift +1 -0
  26. package/ios/Source/sgcs/G1.swift +3 -3
  27. package/ios/Source/sgcs/G2.swift +1022 -640
  28. package/ios/Source/sgcs/Mach1.swift +2 -2
  29. package/ios/Source/sgcs/MentraLive.swift +2 -2
  30. package/ios/Source/sgcs/MentraNex.swift +215 -85
  31. package/ios/Source/sgcs/SGCManager.swift +13 -4
  32. package/ios/Source/sgcs/Simulated.swift +2 -2
  33. package/package.json +1 -1
  34. package/src/BluetoothSdk.types.ts +13 -1
  35. package/src/_private/BluetoothSdkModule.ts +22 -3
  36. package/src/index.ts +3 -0
package/README.md CHANGED
@@ -409,7 +409,7 @@ For bare native iOS apps, use the public SwiftPM repository:
409
409
  https://github.com/Mentra-Community/mentra-bluetooth-sdk-ios.git
410
410
  ```
411
411
 
412
- Add the `MentraBluetoothSDK` product to your app target at version `0.1.7` or newer.
412
+ Add the `MentraBluetoothSDK` product to your app target at version `0.1.11` or newer.
413
413
 
414
414
  For local SDK development, add this package folder directly in Xcode:
415
415
 
@@ -2,8 +2,11 @@ package com.mentra.bluetoothsdk
2
2
 
3
3
  import com.mentra.bluetoothsdk.debug.BleTraceLogger
4
4
  import com.mentra.bluetoothsdk.utils.DeviceTypes
5
+ import expo.modules.kotlin.functions.Coroutine
5
6
  import expo.modules.kotlin.modules.Module
6
7
  import expo.modules.kotlin.modules.ModuleDefinition
8
+ import kotlinx.coroutines.Dispatchers
9
+ import kotlinx.coroutines.withContext
7
10
 
8
11
  class BluetoothSdkModule : Module() {
9
12
  private var sdk: MentraBluetoothSdk? = null
@@ -195,6 +198,9 @@ class BluetoothSdkModule : Module() {
195
198
  "glasses_not_ready",
196
199
  "button_press",
197
200
  "touch_event",
201
+ "accel_event",
202
+ "CompassHeadingEvent",
203
+ "CompassCalibrationEvent",
198
204
  "head_up",
199
205
  "voice_activity_detection_status",
200
206
  "speaking_status",
@@ -534,7 +540,12 @@ class BluetoothSdkModule : Module() {
534
540
  )
535
541
  }
536
542
 
537
- AsyncFunction("restartTranscriber") { deviceManager?.restartTranscriber() }
543
+ // Runs on Dispatchers.IO, not the shared Expo AsyncFunctionQueue: restart()
544
+ // does a synchronous JNI model reload that would otherwise block every other
545
+ // native call in the app until it completes.
546
+ AsyncFunction("restartTranscriber") Coroutine { ->
547
+ withContext(Dispatchers.IO) { deviceManager?.restartTranscriber() }
548
+ }
538
549
 
539
550
  // MARK: - Audio Playback Monitoring
540
551
 
@@ -542,14 +553,17 @@ class BluetoothSdkModule : Module() {
542
553
  sdk?.setOwnAppAudioPlaying(playing)
543
554
  }
544
555
 
545
- AsyncFunction("getGlassesMediaVolume") {
556
+ // *Blocking on Dispatchers.IO, not the shared AsyncFunctionQueue: these wait on
557
+ // a CountDownLatch (up to 5s) for a BLE round-trip, which would otherwise stall
558
+ // every other native call queued behind them.
559
+ AsyncFunction("getGlassesMediaVolume") Coroutine { ->
546
560
  val cm = deviceManager ?: throw IllegalStateException("device_manager_null")
547
- cm.getGlassesMediaVolumeBlocking()
561
+ withContext(Dispatchers.IO) { cm.getGlassesMediaVolumeBlocking() }
548
562
  }
549
563
 
550
- AsyncFunction("setGlassesMediaVolume") { level: Int ->
564
+ AsyncFunction("setGlassesMediaVolume") Coroutine { level: Int ->
551
565
  val cm = deviceManager ?: throw IllegalStateException("device_manager_null")
552
- cm.setGlassesMediaVolumeBlocking(level)
566
+ withContext(Dispatchers.IO) { cm.setGlassesMediaVolumeBlocking(level) }
553
567
  }
554
568
 
555
569
  // MARK: - RGB LED Control
@@ -605,8 +619,13 @@ class BluetoothSdkModule : Module() {
605
619
  com.mentra.bluetoothsdk.stt.STTTools.validateSTTModel(path)
606
620
  }
607
621
 
608
- AsyncFunction("extractTarBz2") { sourcePath: String, destinationPath: String ->
609
- com.mentra.bluetoothsdk.stt.STTTools.extractTarBz2(sourcePath, destinationPath)
622
+ // Runs on Dispatchers.IO, not the shared Expo AsyncFunctionQueue: bz2/tar
623
+ // extraction of the 100–350MB model is a multi-minute, CPU-bound job. On the
624
+ // shared queue it froze every other native call in the app until it finished.
625
+ AsyncFunction("extractTarBz2") Coroutine { sourcePath: String, destinationPath: String ->
626
+ withContext(Dispatchers.IO) {
627
+ com.mentra.bluetoothsdk.stt.STTTools.extractTarBz2(sourcePath, destinationPath)
628
+ }
610
629
  }
611
630
 
612
631
  // MARK: - TTS Commands
@@ -647,7 +666,9 @@ class BluetoothSdkModule : Module() {
647
666
  com.mentra.core.tts.TTSTools.validateTTSModel(path)
648
667
  }
649
668
 
650
- AsyncFunction("generateTtsAudio") {
669
+ // Runs on Dispatchers.IO, not the shared Expo AsyncFunctionQueue: TTS synthesis
670
+ // is a synchronous JNI call that would otherwise block other native calls.
671
+ AsyncFunction("generateTtsAudio") Coroutine {
651
672
  text: String,
652
673
  modelPath: String,
653
674
  outputPath: String,
@@ -657,14 +678,16 @@ class BluetoothSdkModule : Module() {
657
678
  appContext.reactContext
658
679
  ?: appContext.currentActivity
659
680
  ?: throw IllegalStateException("No context available")
660
- com.mentra.core.tts.TTSTools.generateTtsAudio(
661
- context,
662
- text,
663
- modelPath,
664
- outputPath,
665
- speakerId,
666
- speed.toFloat()
667
- )
681
+ withContext(Dispatchers.IO) {
682
+ com.mentra.core.tts.TTSTools.generateTtsAudio(
683
+ context,
684
+ text,
685
+ modelPath,
686
+ outputPath,
687
+ speakerId,
688
+ speed.toFloat()
689
+ )
690
+ }
668
691
  }
669
692
  }
670
693
  }
@@ -681,6 +681,20 @@ public class Bridge private constructor() {
681
681
  sendTypedMessage("imu_gesture_event", eventBody as Map<String, Any>)
682
682
  }
683
683
 
684
+ /**
685
+ * Send a single accelerometer reading from the glasses IMU - matches iOS
686
+ * Bridge.sendAccelEvent. A richer combined IMU event (gyro + magnetometer) is future work.
687
+ */
688
+ @JvmStatic
689
+ fun sendAccelEvent(x: Float, y: Float, z: Float, timestamp: Long) {
690
+ val body = HashMap<String, Any>()
691
+ body["x"] = x
692
+ body["y"] = y
693
+ body["z"] = z
694
+ body["timestamp"] = timestamp
695
+ sendTypedMessage("accel_event", body as Map<String, Any>)
696
+ }
697
+
684
698
  // Arbitrary WS Comms (don't use these, make a dedicated function for your use case):
685
699
 
686
700
  /** Send WebSocket text message */
@@ -27,6 +27,9 @@ import com.mentra.bluetoothsdk.utils.MicMap
27
27
  import com.mentra.bluetoothsdk.utils.MicTypes
28
28
  import com.mentra.lc3Lib.Lc3Cpp
29
29
  import com.mentra.bluetoothsdk.stt.SherpaOnnxTranscriber
30
+ import kotlinx.coroutines.CoroutineScope
31
+ import kotlinx.coroutines.Dispatchers
32
+ import kotlinx.coroutines.launch
30
33
  import java.text.SimpleDateFormat
31
34
  import java.util.*
32
35
  import java.util.concurrent.CountDownLatch
@@ -1124,8 +1127,22 @@ class DeviceManager {
1124
1127
 
1125
1128
  syncSystemTimeOnceForConnection(readyKey)
1126
1129
 
1127
- // Apply dashboard position before any boot text so content doesn't jump.
1128
- sgc?.setDashboardPosition(dashboardHeight, dashboardDepth)
1130
+ // re-apply display height/depth after reconnection
1131
+ mainHandler.postDelayed(
1132
+ {
1133
+ val h =
1134
+ (DeviceStore.store.get("bluetooth", "dashboard_height") as? Number)
1135
+ ?.toInt()
1136
+ ?: 4
1137
+ val rawDepth =
1138
+ (DeviceStore.store.get("bluetooth", "dashboard_depth") as? Number)
1139
+ ?.toInt()
1140
+ ?: dashboardDepth // canonical default (2), not 1
1141
+ val d = rawDepth.coerceIn(1, 4)
1142
+ sgc?.setDashboardPosition(h, d)
1143
+ },
1144
+ 2000
1145
+ )
1129
1146
 
1130
1147
  // Show welcome message on first connect for all display glasses
1131
1148
  if (shouldSendBootingMessage) {
@@ -1288,7 +1305,9 @@ class DeviceManager {
1288
1305
  }
1289
1306
 
1290
1307
  fun showNotificationsPanel() {
1291
- sgc?.showNotificationsPanel()
1308
+ CoroutineScope(Dispatchers.Main).launch {
1309
+ sgc?.showNotificationsPanel()
1310
+ }
1292
1311
  }
1293
1312
 
1294
1313
  fun ping() {
@@ -121,6 +121,7 @@ object DeviceStore {
121
121
  store.set("bluetooth", "dashboard_height", 4)
122
122
  store.set("bluetooth", "dashboard_depth", 2)
123
123
  store.set("bluetooth", "head_up_angle", 30)
124
+ store.set("bluetooth", "imu_enabled", false)
124
125
  store.set("bluetooth", "contextual_dashboard", true)
125
126
  store.set("bluetooth", "gallery_mode", true)
126
127
  store.set("bluetooth", "voice_activity_detection_enabled", BluetoothSdkDefaults.VOICE_ACTIVITY_DETECTION_ENABLED)
@@ -238,6 +239,13 @@ object DeviceStore {
238
239
  DeviceManager.getInstance().sgc?.setHeadUpAngle(angle)
239
240
  }
240
241
  }
242
+ "bluetooth" to "imu_enabled" -> {
243
+ (value as? Boolean)?.let { enabled ->
244
+ CoroutineScope(Dispatchers.Main).launch {
245
+ DeviceManager.getInstance().sgc?.setImuEnabled(enabled)
246
+ }
247
+ }
248
+ }
241
249
  "bluetooth" to "menu_apps" -> {
242
250
  @Suppress("UNCHECKED_CAST")
243
251
  (value as? List<Map<String, Any>>)?.let { items ->
@@ -16,7 +16,8 @@ enum class PhotoSize(val value: String) {
16
16
  enum class ButtonPhotoSize(val value: String) {
17
17
  SMALL("small"),
18
18
  MEDIUM("medium"),
19
- LARGE("large");
19
+ LARGE("large"),
20
+ MAX("max");
20
21
 
21
22
  companion object {
22
23
  @JvmStatic