@mentra/bluetooth-sdk 0.1.2 → 0.1.3

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 (59) hide show
  1. package/README.md +33 -19
  2. package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +13 -13
  3. package/android/src/main/java/com/mentra/bluetoothsdk/Bridge.kt +42 -18
  4. package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +19 -21
  5. package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +4 -4
  6. package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothModels.kt +118 -103
  7. package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +110 -38
  8. package/android/src/main/java/com/mentra/bluetoothsdk/controllers/ControllerManager.kt +4 -4
  9. package/android/src/main/java/com/mentra/bluetoothsdk/controllers/R1.kt +2 -2
  10. package/android/src/main/java/com/mentra/bluetoothsdk/services/PhoneMic.kt +6 -6
  11. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G1.java +1 -1
  12. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G2.kt +3 -3
  13. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Mach1.java +1 -1
  14. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.java +18 -18
  15. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraNex.kt +1 -1
  16. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/SGCManager.kt +4 -4
  17. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Simulated.kt +2 -2
  18. package/android/src/main/java/com/mentra/bluetoothsdk/utils/Constants.kt +6 -6
  19. package/build/BluetoothSdk.types.d.ts +126 -27
  20. package/build/BluetoothSdk.types.d.ts.map +1 -1
  21. package/build/BluetoothSdk.types.js.map +1 -1
  22. package/build/_internal.d.ts +12 -0
  23. package/build/_internal.d.ts.map +1 -0
  24. package/build/_internal.js +11 -0
  25. package/build/_internal.js.map +1 -0
  26. package/build/{BluetoothSdkModule.d.ts → _private/BluetoothSdkModule.d.ts} +19 -8
  27. package/build/_private/BluetoothSdkModule.d.ts.map +1 -0
  28. package/build/{BluetoothSdkModule.js → _private/BluetoothSdkModule.js} +41 -26
  29. package/build/_private/BluetoothSdkModule.js.map +1 -0
  30. package/build/index.d.ts +4 -2
  31. package/build/index.d.ts.map +1 -1
  32. package/build/index.js +3 -4
  33. package/build/index.js.map +1 -1
  34. package/ios/BluetoothSdkModule.swift +14 -9
  35. package/ios/Source/Bridge.swift +44 -12
  36. package/ios/Source/DeviceManager.swift +27 -26
  37. package/ios/Source/DeviceStore.swift +4 -4
  38. package/ios/Source/MentraBluetoothSDK.swift +250 -132
  39. package/ios/Source/controllers/ControllerManager.swift +5 -5
  40. package/ios/Source/controllers/R1.swift +3 -3
  41. package/ios/Source/services/PhoneMic.swift +5 -5
  42. package/ios/Source/sgcs/Frame.swift +1 -1
  43. package/ios/Source/sgcs/G1.swift +1 -1
  44. package/ios/Source/sgcs/G2.swift +3 -3
  45. package/ios/Source/sgcs/Mach1.swift +1 -1
  46. package/ios/Source/sgcs/MentraLive.swift +31 -33
  47. package/ios/Source/sgcs/MentraNex.swift +2 -2
  48. package/ios/Source/sgcs/SGCManager.swift +5 -5
  49. package/ios/Source/sgcs/Simulated.swift +3 -3
  50. package/ios/Source/utils/Constants.swift +6 -6
  51. package/ios/Source/utils/JSCExperiment.swift +8 -8
  52. package/ios/Source/utils/TarBz2Extractor.swift +2 -2
  53. package/package.json +13 -1
  54. package/src/BluetoothSdk.types.ts +186 -28
  55. package/src/_internal.ts +11 -0
  56. package/src/{BluetoothSdkModule.ts → _private/BluetoothSdkModule.ts} +71 -38
  57. package/src/index.ts +80 -4
  58. package/build/BluetoothSdkModule.d.ts.map +0 -1
  59. package/build/BluetoothSdkModule.js.map +0 -1
@@ -52,10 +52,10 @@ data class Device(
52
52
 
53
53
  companion object {
54
54
  internal fun fromMap(values: Map<String, Any>): Device? {
55
- val model = stringValue(values, "model", "deviceModel", "device_model") ?: return null
56
- val name = stringValue(values, "name", "deviceName", "device_name") ?: return null
57
- val address = stringValue(values, "address", "deviceAddress", "device_address")?.takeIf { it.isNotBlank() }
58
- val rssi = numberValue(values, "rssi", "signalStrength", "signal_strength")
55
+ val model = stringValue(values, "model") ?: return null
56
+ val name = stringValue(values, "name") ?: return null
57
+ val address = stringValue(values, "address")?.takeIf { it.isNotBlank() }
58
+ val rssi = numberValue(values, "rssi")
59
59
  val id = stringValue(values, "id")?.takeIf { it.isNotBlank() } ?: address ?: "${model}:$name"
60
60
  return Device(
61
61
  model = DeviceModel.fromDeviceType(model),
@@ -94,9 +94,8 @@ data class WifiScanResult(
94
94
  internal fun fromMap(values: Map<String, Any>): WifiScanResult =
95
95
  WifiScanResult(
96
96
  ssid = stringValue(values, "ssid") ?: "",
97
- requiresPassword =
98
- boolValue(values, "requiresPassword", "requires_password", "auth_required") ?: false,
99
- signalStrength = numberValue(values, "signalStrength", "signal_strength", "rssi") ?: -1,
97
+ requiresPassword = boolValue(values, "requiresPassword") ?: false,
98
+ signalStrength = numberValue(values, "signalStrength") ?: -1,
100
99
  frequency = numberValue(values, "frequency"),
101
100
  )
102
101
  }
@@ -107,7 +106,7 @@ data class GlassesStatus(
107
106
  val connected: Boolean,
108
107
  val micEnabled: Boolean,
109
108
  val connectionState: GlassesConnectionState,
110
- val btcConnected: Boolean,
109
+ val bluetoothClassicConnected: Boolean,
111
110
  val signalStrength: Int,
112
111
  val signalStrengthUpdatedAt: Long,
113
112
  val deviceModel: String,
@@ -115,7 +114,7 @@ data class GlassesStatus(
115
114
  val firmwareVersion: String,
116
115
  val besFirmwareVersion: String,
117
116
  val mtkFirmwareVersion: String,
118
- val btMacAddress: String,
117
+ val bluetoothMacAddress: String,
119
118
  val leftMacAddress: String,
120
119
  val rightMacAddress: String,
121
120
  val macAddress: String,
@@ -146,15 +145,15 @@ data class GlassesStatus(
146
145
  mapOf(
147
146
  "connection" to connectionState.toStatusMap(connected, fullyBooted),
148
147
  "micEnabled" to micEnabled,
149
- "btcConnected" to btcConnected,
148
+ "bluetoothClassicConnected" to bluetoothClassicConnected,
150
149
  "signalStrength" to signalStrength,
151
150
  "signalStrengthUpdatedAt" to signalStrengthUpdatedAt,
152
151
  "deviceModel" to deviceModel,
153
152
  "androidVersion" to androidVersion,
154
- "fwVersion" to firmwareVersion,
155
- "besFwVersion" to besFirmwareVersion,
156
- "mtkFwVersion" to mtkFirmwareVersion,
157
- "btMacAddress" to btMacAddress,
153
+ "firmwareVersion" to firmwareVersion,
154
+ "besFirmwareVersion" to besFirmwareVersion,
155
+ "mtkFirmwareVersion" to mtkFirmwareVersion,
156
+ "bluetoothMacAddress" to bluetoothMacAddress,
158
157
  "leftMacAddress" to leftMacAddress,
159
158
  "rightMacAddress" to rightMacAddress,
160
159
  "macAddress" to macAddress,
@@ -189,15 +188,15 @@ data class GlassesStatus(
189
188
  connected = boolValue(values, "connected") ?: false,
190
189
  micEnabled = boolValue(values, "micEnabled") ?: false,
191
190
  connectionState = GlassesConnectionState.fromValue(stringValue(values, "connectionState")),
192
- btcConnected = boolValue(values, "btcConnected") ?: false,
191
+ bluetoothClassicConnected = boolValue(values, "bluetoothClassicConnected") ?: false,
193
192
  signalStrength = numberValue(values, "signalStrength") ?: -1,
194
193
  signalStrengthUpdatedAt = longValue(values, "signalStrengthUpdatedAt") ?: 0L,
195
194
  deviceModel = stringValue(values, "deviceModel") ?: "",
196
195
  androidVersion = stringValue(values, "androidVersion") ?: "",
197
- firmwareVersion = stringValue(values, "firmwareVersion", "fwVersion") ?: "",
198
- besFirmwareVersion = stringValue(values, "besFwVersion", "besFirmwareVersion") ?: "",
199
- mtkFirmwareVersion = stringValue(values, "mtkFwVersion", "mtkFirmwareVersion") ?: "",
200
- btMacAddress = stringValue(values, "btMacAddress") ?: "",
196
+ firmwareVersion = stringValue(values, "firmwareVersion") ?: "",
197
+ besFirmwareVersion = stringValue(values, "besFirmwareVersion") ?: "",
198
+ mtkFirmwareVersion = stringValue(values, "mtkFirmwareVersion") ?: "",
199
+ bluetoothMacAddress = stringValue(values, "bluetoothMacAddress") ?: "",
201
200
  leftMacAddress = stringValue(values, "leftMacAddress") ?: "",
202
201
  rightMacAddress = stringValue(values, "rightMacAddress") ?: "",
203
202
  macAddress = stringValue(values, "macAddress") ?: "",
@@ -234,6 +233,11 @@ data class BluetoothStatus(
234
233
  val micEnabled: Boolean,
235
234
  val currentMic: String,
236
235
  val micRanking: List<String>,
236
+ /**
237
+ * Nearby glasses in stable discovery order. Existing entries keep their array position as
238
+ * details refresh; new glasses append at the end, and removals should not reorder remaining
239
+ * entries.
240
+ */
237
241
  val searchResults: List<Device>,
238
242
  val wifiScanResults: List<WifiScanResult>,
239
243
  val lastLog: List<String>,
@@ -261,7 +265,7 @@ data class BluetoothStatus(
261
265
  val buttonMaxRecordingTime: Int,
262
266
  val buttonVideoWidth: Int,
263
267
  val buttonVideoHeight: Int,
264
- val buttonVideoFps: Int,
268
+ val buttonVideoFrameRate: Int,
265
269
  val shouldSendPcm: Boolean,
266
270
  val shouldSendLc3: Boolean,
267
271
  val shouldSendTranscript: Boolean,
@@ -309,13 +313,13 @@ data class BluetoothStatus(
309
313
  "dashboard_depth" to dashboardDepth,
310
314
  "head_up_angle" to headUpAngle,
311
315
  "contextual_dashboard" to contextualDashboard,
312
- "gallery_mode" to galleryModeAuto,
316
+ "galleryModeAuto" to galleryModeAuto,
313
317
  "button_photo_size" to buttonPhotoSize.value,
314
318
  "button_camera_led" to buttonCameraLed,
315
319
  "button_max_recording_time" to buttonMaxRecordingTime,
316
320
  "button_video_width" to buttonVideoWidth,
317
321
  "button_video_height" to buttonVideoHeight,
318
- "button_video_fps" to buttonVideoFps,
322
+ "button_video_fps" to buttonVideoFrameRate,
319
323
  "should_send_pcm" to shouldSendPcm,
320
324
  "should_send_lc3" to shouldSendLc3,
321
325
  "should_send_transcript" to shouldSendTranscript,
@@ -357,13 +361,13 @@ data class BluetoothStatus(
357
361
  dashboardDepth = numberValue(values, "dashboard_depth") ?: 2,
358
362
  headUpAngle = numberValue(values, "head_up_angle") ?: 30,
359
363
  contextualDashboard = boolValue(values, "contextual_dashboard") ?: true,
360
- galleryModeAuto = boolValue(values, "gallery_mode") ?: true,
364
+ galleryModeAuto = boolValue(values, "galleryModeAuto") ?: true,
361
365
  buttonPhotoSize = ButtonPhotoSize.fromValue(stringValue(values, "button_photo_size")),
362
366
  buttonCameraLed = boolValue(values, "button_camera_led") ?: true,
363
367
  buttonMaxRecordingTime = numberValue(values, "button_max_recording_time") ?: 10,
364
368
  buttonVideoWidth = numberValue(values, "button_video_width") ?: 1280,
365
369
  buttonVideoHeight = numberValue(values, "button_video_height") ?: 720,
366
- buttonVideoFps = numberValue(values, "button_video_fps") ?: 30,
370
+ buttonVideoFrameRate = numberValue(values, "button_video_fps") ?: 30,
367
371
  shouldSendPcm = boolValue(values, "should_send_pcm") ?: false,
368
372
  shouldSendLc3 = boolValue(values, "should_send_lc3") ?: false,
369
373
  shouldSendTranscript = boolValue(values, "should_send_transcript") ?: false,
@@ -380,7 +384,7 @@ data class GlassesStatusUpdate(
380
384
  val connected: Boolean? = null,
381
385
  val micEnabled: Boolean? = null,
382
386
  val connectionState: GlassesConnectionState? = null,
383
- val btcConnected: Boolean? = null,
387
+ val bluetoothClassicConnected: Boolean? = null,
384
388
  val signalStrength: Int? = null,
385
389
  val signalStrengthUpdatedAt: Long? = null,
386
390
  val deviceModel: String? = null,
@@ -388,7 +392,7 @@ data class GlassesStatusUpdate(
388
392
  val firmwareVersion: String? = null,
389
393
  val besFirmwareVersion: String? = null,
390
394
  val mtkFirmwareVersion: String? = null,
391
- val btMacAddress: String? = null,
395
+ val bluetoothMacAddress: String? = null,
392
396
  val leftMacAddress: String? = null,
393
397
  val rightMacAddress: String? = null,
394
398
  val macAddress: String? = null,
@@ -428,15 +432,15 @@ data class GlassesStatusUpdate(
428
432
  put("connection", state.toStatusMap(connected == true, fullyBooted == true))
429
433
  }
430
434
  putIfNotNull("micEnabled", micEnabled)
431
- putIfNotNull("btcConnected", btcConnected)
435
+ putIfNotNull("bluetoothClassicConnected", bluetoothClassicConnected)
432
436
  putIfNotNull("signalStrength", signalStrength)
433
437
  putIfNotNull("signalStrengthUpdatedAt", signalStrengthUpdatedAt)
434
438
  putIfNotNull("deviceModel", deviceModel)
435
439
  putIfNotNull("androidVersion", androidVersion)
436
- putIfNotNull("fwVersion", firmwareVersion)
437
- putIfNotNull("besFwVersion", besFirmwareVersion)
438
- putIfNotNull("mtkFwVersion", mtkFirmwareVersion)
439
- putIfNotNull("btMacAddress", btMacAddress)
440
+ putIfNotNull("firmwareVersion", firmwareVersion)
441
+ putIfNotNull("besFirmwareVersion", besFirmwareVersion)
442
+ putIfNotNull("mtkFirmwareVersion", mtkFirmwareVersion)
443
+ putIfNotNull("bluetoothMacAddress", bluetoothMacAddress)
440
444
  putIfNotNull("leftMacAddress", leftMacAddress)
441
445
  putIfNotNull("rightMacAddress", rightMacAddress)
442
446
  putIfNotNull("macAddress", macAddress)
@@ -475,15 +479,15 @@ data class GlassesStatusUpdate(
475
479
  connected = optionalBoolValue(values, "connected"),
476
480
  micEnabled = optionalBoolValue(values, "micEnabled"),
477
481
  connectionState = GlassesConnectionState.optionalFromValue(optionalStringValue(values, "connectionState")),
478
- btcConnected = optionalBoolValue(values, "btcConnected"),
482
+ bluetoothClassicConnected = optionalBoolValue(values, "bluetoothClassicConnected"),
479
483
  signalStrength = optionalNumberValue(values, "signalStrength"),
480
484
  signalStrengthUpdatedAt = optionalLongValue(values, "signalStrengthUpdatedAt"),
481
485
  deviceModel = optionalStringValue(values, "deviceModel"),
482
486
  androidVersion = optionalStringValue(values, "androidVersion"),
483
- firmwareVersion = optionalStringValue(values, "firmwareVersion", "fwVersion"),
484
- besFirmwareVersion = optionalStringValue(values, "besFwVersion", "besFirmwareVersion"),
485
- mtkFirmwareVersion = optionalStringValue(values, "mtkFwVersion", "mtkFirmwareVersion"),
486
- btMacAddress = optionalStringValue(values, "btMacAddress"),
487
+ firmwareVersion = optionalStringValue(values, "firmwareVersion"),
488
+ besFirmwareVersion = optionalStringValue(values, "besFirmwareVersion"),
489
+ mtkFirmwareVersion = optionalStringValue(values, "mtkFirmwareVersion"),
490
+ bluetoothMacAddress = optionalStringValue(values, "bluetoothMacAddress"),
487
491
  leftMacAddress = optionalStringValue(values, "leftMacAddress"),
488
492
  rightMacAddress = optionalStringValue(values, "rightMacAddress"),
489
493
  macAddress = optionalStringValue(values, "macAddress"),
@@ -511,7 +515,7 @@ data class GlassesStatusUpdate(
511
515
  hotspot =
512
516
  if (hasAnyKey(values, "hotspot")) {
513
517
  HotspotStatus.fromMap(values)
514
- } else if (hasAnyKey(values, "hotspotEnabled", "hotspotSsid", "hotspotPassword", "hotspotGatewayIp", "hotspotLocalIp")) {
518
+ } else if (hasAnyKey(values, "hotspotEnabled", "hotspotSsid", "hotspotPassword", "hotspotGatewayIp")) {
515
519
  HotspotStatus.fromStoreMap(values)
516
520
  } else {
517
521
  null
@@ -534,6 +538,11 @@ data class BluetoothStatusUpdate(
534
538
  val micEnabled: Boolean? = null,
535
539
  val currentMic: String? = null,
536
540
  val micRanking: List<String>? = null,
541
+ /**
542
+ * Nearby glasses in stable discovery order when included in an update. Existing entries keep
543
+ * their array position as details refresh; new glasses append at the end, and removals should
544
+ * not reorder remaining entries.
545
+ */
537
546
  val searchResults: List<Device>? = null,
538
547
  val wifiScanResults: List<WifiScanResult>? = null,
539
548
  val lastLog: List<String>? = null,
@@ -561,7 +570,7 @@ data class BluetoothStatusUpdate(
561
570
  val buttonMaxRecordingTime: Int? = null,
562
571
  val buttonVideoWidth: Int? = null,
563
572
  val buttonVideoHeight: Int? = null,
564
- val buttonVideoFps: Int? = null,
573
+ val buttonVideoFrameRate: Int? = null,
565
574
  val shouldSendPcm: Boolean? = null,
566
575
  val shouldSendLc3: Boolean? = null,
567
576
  val shouldSendTranscript: Boolean? = null,
@@ -599,13 +608,13 @@ data class BluetoothStatusUpdate(
599
608
  putIfNotNull("dashboard_depth", dashboardDepth)
600
609
  putIfNotNull("head_up_angle", headUpAngle)
601
610
  putIfNotNull("contextual_dashboard", contextualDashboard)
602
- putIfNotNull("gallery_mode", galleryModeAuto)
611
+ putIfNotNull("galleryModeAuto", galleryModeAuto)
603
612
  buttonPhotoSize?.let { put("button_photo_size", it.value) }
604
613
  putIfNotNull("button_camera_led", buttonCameraLed)
605
614
  putIfNotNull("button_max_recording_time", buttonMaxRecordingTime)
606
615
  putIfNotNull("button_video_width", buttonVideoWidth)
607
616
  putIfNotNull("button_video_height", buttonVideoHeight)
608
- putIfNotNull("button_video_fps", buttonVideoFps)
617
+ putIfNotNull("button_video_fps", buttonVideoFrameRate)
609
618
  putIfNotNull("should_send_pcm", shouldSendPcm)
610
619
  putIfNotNull("should_send_lc3", shouldSendLc3)
611
620
  putIfNotNull("should_send_transcript", shouldSendTranscript)
@@ -649,14 +658,14 @@ data class BluetoothStatusUpdate(
649
658
  dashboardDepth = optionalNumberValue(values, "dashboard_depth"),
650
659
  headUpAngle = optionalNumberValue(values, "head_up_angle"),
651
660
  contextualDashboard = optionalBoolValue(values, "contextual_dashboard"),
652
- galleryModeAuto = optionalBoolValue(values, "gallery_mode"),
661
+ galleryModeAuto = optionalBoolValue(values, "galleryModeAuto"),
653
662
  buttonPhotoSize =
654
663
  optionalStringValue(values, "button_photo_size")?.let(ButtonPhotoSize::fromValue),
655
664
  buttonCameraLed = optionalBoolValue(values, "button_camera_led"),
656
665
  buttonMaxRecordingTime = optionalNumberValue(values, "button_max_recording_time"),
657
666
  buttonVideoWidth = optionalNumberValue(values, "button_video_width"),
658
667
  buttonVideoHeight = optionalNumberValue(values, "button_video_height"),
659
- buttonVideoFps = optionalNumberValue(values, "button_video_fps"),
668
+ buttonVideoFrameRate = optionalNumberValue(values, "button_video_fps"),
660
669
  shouldSendPcm = optionalBoolValue(values, "should_send_pcm"),
661
670
  shouldSendLc3 = optionalBoolValue(values, "should_send_lc3"),
662
671
  shouldSendTranscript = optionalBoolValue(values, "should_send_transcript"),
@@ -683,7 +692,7 @@ data class DisplayTextRequest(
683
692
  )
684
693
  }
685
694
 
686
- data class DisplayEventRequest(
695
+ internal data class DisplayEventRequest(
687
696
  val values: Map<String, Any>,
688
697
  ) {
689
698
  fun toMap(): Map<String, Any> = values
@@ -694,7 +703,7 @@ data class DashboardPositionRequest(
694
703
  val depth: Int,
695
704
  )
696
705
 
697
- data class DashboardMenuItem(
706
+ internal data class DashboardMenuItem(
698
707
  val title: String,
699
708
  val packageName: String,
700
709
  val values: Map<String, Any> = emptyMap(),
@@ -755,7 +764,7 @@ data class ButtonPhotoSettings(
755
764
  data class ButtonVideoRecordingSettings(
756
765
  val width: Int,
757
766
  val height: Int,
758
- val fps: Int,
767
+ val frameRate: Int,
759
768
  )
760
769
 
761
770
  enum class CameraFov(val fov: Int, val roiPosition: Int) {
@@ -763,19 +772,11 @@ enum class CameraFov(val fov: Int, val roiPosition: Int) {
763
772
  WIDE(118, 0),
764
773
  }
765
774
 
766
- data class MicConfig(
767
- val sendPcmData: Boolean,
768
- val sendTranscript: Boolean,
769
- val bypassVad: Boolean,
770
- val sendLc3Data: Boolean = false,
771
- )
772
-
773
775
  enum class MicPreference(val value: String) {
774
776
  AUTO("auto"),
775
777
  PHONE("phone"),
776
778
  GLASSES("glasses"),
777
- BT_CLASSIC("btclassic"),
778
- BT("bt"),
779
+ BLUETOOTH("bluetooth"),
779
780
  }
780
781
 
781
782
  data class PhotoRequest @JvmOverloads constructor(
@@ -785,7 +786,6 @@ data class PhotoRequest @JvmOverloads constructor(
785
786
  val webhookUrl: String,
786
787
  val authToken: String? = null,
787
788
  val compress: PhotoCompression = PhotoCompression.MEDIUM,
788
- val flash: Boolean = false,
789
789
  val sound: Boolean = true,
790
790
  )
791
791
 
@@ -805,13 +805,13 @@ data class StreamVideoConfig @JvmOverloads constructor(
805
805
 
806
806
  companion object {
807
807
  @JvmStatic
808
- fun fromMap(values: Map<*, *>?): StreamVideoConfig? {
808
+ fun fromMap(values: Map<String, Any>?): StreamVideoConfig? {
809
809
  values ?: return null
810
810
  return StreamVideoConfig(
811
- width = numberValue(values, "width", "w"),
812
- height = numberValue(values, "height", "h"),
813
- bitrate = numberValue(values, "bitrate", "br"),
814
- frameRate = numberValue(values, "frameRate", "fr"),
811
+ width = numberValue(values, "width"),
812
+ height = numberValue(values, "height"),
813
+ bitrate = numberValue(values, "bitrate"),
814
+ frameRate = numberValue(values, "frameRate"),
815
815
  )
816
816
  }
817
817
  }
@@ -833,13 +833,13 @@ data class StreamAudioConfig @JvmOverloads constructor(
833
833
 
834
834
  companion object {
835
835
  @JvmStatic
836
- fun fromMap(values: Map<*, *>?): StreamAudioConfig? {
836
+ fun fromMap(values: Map<String, Any>?): StreamAudioConfig? {
837
837
  values ?: return null
838
838
  return StreamAudioConfig(
839
- bitrate = numberValue(values, "bitrate", "br"),
840
- sampleRate = numberValue(values, "sampleRate", "sr"),
841
- echoCancellation = values["echoCancellation"] as? Boolean ?: values["ec"] as? Boolean,
842
- noiseSuppression = values["noiseSuppression"] as? Boolean ?: values["ns"] as? Boolean,
839
+ bitrate = numberValue(values, "bitrate"),
840
+ sampleRate = numberValue(values, "sampleRate"),
841
+ echoCancellation = values["echoCancellation"] as? Boolean,
842
+ noiseSuppression = values["noiseSuppression"] as? Boolean,
843
843
  )
844
844
  }
845
845
  }
@@ -850,7 +850,6 @@ data class StreamRequest @JvmOverloads constructor(
850
850
  val streamId: String = "",
851
851
  val keepAlive: Boolean = true,
852
852
  val keepAliveIntervalSeconds: Int = 15,
853
- val flash: Boolean = true,
854
853
  val sound: Boolean = true,
855
854
  val video: StreamVideoConfig? = null,
856
855
  val audio: StreamAudioConfig? = null,
@@ -863,7 +862,8 @@ data class StreamRequest @JvmOverloads constructor(
863
862
  values["streamId"] = streamId
864
863
  values["keepAlive"] = keepAlive
865
864
  values["keepAliveIntervalSeconds"] = keepAliveIntervalSeconds
866
- values["flash"] = flash
865
+ // The camera light is a privacy indicator and cannot be disabled by SDK callers.
866
+ values["flash"] = true
867
867
  values["sound"] = sound
868
868
  video?.toMap()?.takeIf { it.isNotEmpty() }?.let { values["video"] = it }
869
869
  audio?.toMap()?.takeIf { it.isNotEmpty() }?.let { values["audio"] = it }
@@ -880,10 +880,9 @@ data class StreamRequest @JvmOverloads constructor(
880
880
  streamId = values["streamId"] as? String ?: "",
881
881
  keepAlive = values["keepAlive"] as? Boolean ?: true,
882
882
  keepAliveIntervalSeconds = (values["keepAliveIntervalSeconds"] as? Number)?.toInt() ?: 15,
883
- flash = values["flash"] as? Boolean ?: true,
884
883
  sound = values["sound"] as? Boolean ?: true,
885
- video = StreamVideoConfig.fromMap((values["video"] ?: values["v"]) as? Map<*, *>),
886
- audio = StreamAudioConfig.fromMap((values["audio"] ?: values["a"]) as? Map<*, *>),
884
+ video = StreamVideoConfig.fromMap(stringMapValue(values["video"])),
885
+ audio = StreamAudioConfig.fromMap(stringMapValue(values["audio"])),
887
886
  extraValues = values,
888
887
  )
889
888
  }
@@ -943,15 +942,14 @@ data class RgbLedRequest @JvmOverloads constructor(
943
942
  val packageName: String?,
944
943
  val action: RgbLedAction,
945
944
  val color: RgbLedColor?,
946
- val ontime: Int,
947
- val offtime: Int,
945
+ val onDurationMs: Int,
946
+ val offDurationMs: Int,
948
947
  val count: Int,
949
948
  )
950
949
 
951
950
  data class VideoRecordingRequest(
952
951
  val requestId: String,
953
952
  val save: Boolean,
954
- val flash: Boolean,
955
953
  val sound: Boolean,
956
954
  )
957
955
 
@@ -964,8 +962,8 @@ data class ButtonPressEvent(
964
962
  data class TouchEvent(
965
963
  val values: Map<String, Any>,
966
964
  ) {
967
- val deviceModel: String? get() = stringValue(values, "device_model", "deviceModel")
968
- val gestureName: String? get() = stringValue(values, "gesture_name", "gestureName")
965
+ val deviceModel: String? get() = stringValue(values, "deviceModel")
966
+ val gestureName: String? get() = stringValue(values, "gestureName")
969
967
  val timestamp: Long? get() = longValue(values, "timestamp")
970
968
  val isSwipe: Boolean get() = gestureName?.contains("swipe", ignoreCase = true) == true
971
969
  }
@@ -973,8 +971,8 @@ data class TouchEvent(
973
971
  data class SwipeEvent(
974
972
  val values: Map<String, Any>,
975
973
  ) {
976
- val deviceModel: String? get() = stringValue(values, "device_model", "deviceModel")
977
- val gestureName: String? get() = stringValue(values, "gesture_name", "gestureName")
974
+ val deviceModel: String? get() = stringValue(values, "deviceModel")
975
+ val gestureName: String? get() = stringValue(values, "gestureName")
978
976
  val timestamp: Long? get() = longValue(values, "timestamp")
979
977
  }
980
978
 
@@ -1107,7 +1105,7 @@ sealed interface HotspotStatus {
1107
1105
  enabled = enabled,
1108
1106
  ssid = stringValue(values, "hotspotSsid"),
1109
1107
  password = stringValue(values, "hotspotPassword"),
1110
- localIp = stringValue(values, "hotspotGatewayIp", "hotspotLocalIp"),
1108
+ localIp = stringValue(values, "hotspotGatewayIp"),
1111
1109
  )
1112
1110
  }
1113
1111
 
@@ -1152,7 +1150,7 @@ data class HotspotStatusEvent(
1152
1150
  data class HotspotErrorEvent(
1153
1151
  val values: Map<String, Any>,
1154
1152
  ) {
1155
- val message: String? get() = stringValue(values, "error_message", "message", "error")
1153
+ val message: String? get() = stringValue(values, "errorMessage")
1156
1154
  val timestamp: Long? get() = longValue(values, "timestamp")
1157
1155
  }
1158
1156
 
@@ -1170,7 +1168,7 @@ sealed interface PhotoResponse {
1170
1168
  is Success -> mapOf(
1171
1169
  "state" to state,
1172
1170
  "requestId" to requestId,
1173
- "photoUrl" to photoUrl,
1171
+ "uploadUrl" to uploadUrl,
1174
1172
  "timestamp" to timestamp,
1175
1173
  )
1176
1174
 
@@ -1190,7 +1188,7 @@ sealed interface PhotoResponse {
1190
1188
 
1191
1189
  data class Success(
1192
1190
  override val requestId: String,
1193
- val photoUrl: String,
1191
+ val uploadUrl: String,
1194
1192
  override val timestamp: Long,
1195
1193
  ) : PhotoResponse {
1196
1194
  override val state: String = "success"
@@ -1207,19 +1205,17 @@ sealed interface PhotoResponse {
1207
1205
 
1208
1206
  companion object {
1209
1207
  fun fromMap(values: Map<String, Any>): PhotoResponse {
1210
- val requestId = stringValue(values, "requestId", "request_id").orEmpty()
1208
+ val requestId = stringValue(values, "requestId").orEmpty()
1211
1209
  val timestamp = longValue(values, "timestamp") ?: System.currentTimeMillis()
1212
- val state = stringValue(values, "state", "status")?.lowercase()
1213
- val success = boolValue(values, "success")
1214
- return if (state == "success" || success == true) {
1215
- val photoUrl = stringValue(values, "photoUrl", "photo_url").orEmpty()
1216
- Success(requestId = requestId, photoUrl = photoUrl, timestamp = timestamp)
1210
+ val state = stringValue(values, "state")?.lowercase()
1211
+ return if (state == "success") {
1212
+ val uploadUrl = stringValue(values, "uploadUrl").orEmpty()
1213
+ Success(requestId = requestId, uploadUrl = uploadUrl, timestamp = timestamp)
1217
1214
  } else {
1218
1215
  Error(
1219
1216
  requestId = requestId,
1220
- errorCode = stringValue(values, "errorCode", "error_code"),
1221
- errorMessage = stringValue(values, "errorMessage", "error_message", "error")
1222
- ?: "Unknown photo error",
1217
+ errorCode = stringValue(values, "errorCode"),
1218
+ errorMessage = stringValue(values, "errorMessage") ?: "Unknown photo error",
1223
1219
  timestamp = timestamp,
1224
1220
  )
1225
1221
  }
@@ -1369,10 +1365,10 @@ sealed interface StreamStatus {
1369
1365
  val rawState = stringValue(values, "status")
1370
1366
  val streaming = boolValue(values, "streaming")
1371
1367
  val reconnecting = boolValue(values, "reconnecting") ?: false
1372
- val streamId = stringValue(values, "streamId", "stream_id")
1368
+ val streamId = stringValue(values, "streamId")
1373
1369
  val timestamp = longValue(values, "timestamp")
1374
1370
  val attempt = numberValue(values, "attempt")
1375
- val maxAttempts = numberValue(values, "maxAttempts", "max_attempts") ?: 0
1371
+ val maxAttempts = numberValue(values, "maxAttempts") ?: 0
1376
1372
 
1377
1373
  if (streaming != null || hasAnyKey(values, "reconnecting")) {
1378
1374
  return Snapshot(
@@ -1416,7 +1412,7 @@ sealed interface StreamStatus {
1416
1412
  )
1417
1413
  StreamState.ERROR -> Error(
1418
1414
  streamId = streamId,
1419
- errorDetails = stringValue(values, "errorDetails", "error_details", "details", "error", "errorMessage")
1415
+ errorDetails = stringValue(values, "errorDetails")
1420
1416
  ?: if (rawState == "error_not_streaming") "not_streaming" else "Unknown stream error",
1421
1417
  timestamp = timestamp,
1422
1418
  )
@@ -1446,8 +1442,8 @@ data class KeepAliveAckEvent(
1446
1442
  val timestamp: Long?,
1447
1443
  ) {
1448
1444
  constructor(values: Map<String, Any>) : this(
1449
- streamId = stringValue(values, "streamId", "stream_id").orEmpty(),
1450
- ackId = stringValue(values, "ackId", "ack_id").orEmpty(),
1445
+ streamId = stringValue(values, "streamId").orEmpty(),
1446
+ ackId = stringValue(values, "ackId").orEmpty(),
1451
1447
  timestamp = longValue(values, "timestamp"),
1452
1448
  )
1453
1449
 
@@ -1467,14 +1463,14 @@ data class LocalTranscriptionEvent(
1467
1463
  )
1468
1464
 
1469
1465
  data class GlassesMediaVolumeGetResult(
1470
- val volume: Int?,
1466
+ val level: Int?,
1471
1467
  val statusCode: Int?,
1472
1468
  val values: Map<String, Any>,
1473
1469
  ) {
1474
1470
  companion object {
1475
1471
  fun fromMap(values: Map<String, Any>): GlassesMediaVolumeGetResult =
1476
1472
  GlassesMediaVolumeGetResult(
1477
- volume = numberValue(values, "vol", "volume"),
1473
+ level = numberValue(values, "level"),
1478
1474
  statusCode = (values["statusCode"] as? Number)?.toInt(),
1479
1475
  values = values,
1480
1476
  )
@@ -1544,6 +1540,31 @@ enum class ScanStopReason {
1544
1540
  ERROR,
1545
1541
  }
1546
1542
 
1543
+ interface ScanCallback {
1544
+ fun onResults(devices: List<Device>) {}
1545
+ fun onComplete(devices: List<Device>) {}
1546
+ fun onError(error: BluetoothError) {}
1547
+ }
1548
+
1549
+ abstract class MentraBluetoothScanCallback : ScanCallback
1550
+
1551
+ class ScanSession internal constructor(
1552
+ private val stopAction: () -> Unit,
1553
+ ) {
1554
+ @Volatile
1555
+ private var stopped = false
1556
+
1557
+ fun stop() {
1558
+ if (stopped) return
1559
+ stopped = true
1560
+ stopAction()
1561
+ }
1562
+
1563
+ internal fun markStopped() {
1564
+ stopped = true
1565
+ }
1566
+ }
1567
+
1547
1568
  interface MentraBluetoothSdkListener {
1548
1569
  fun onGlassesStatusChanged(status: GlassesStatusUpdate) {}
1549
1570
  fun onBluetoothStatusChanged(status: BluetoothStatusUpdate) {}
@@ -1572,12 +1593,6 @@ interface MentraBluetoothSdkListener {
1572
1593
 
1573
1594
  abstract class MentraBluetoothSdkCallback : MentraBluetoothSdkListener
1574
1595
 
1575
- private fun numberValue(
1576
- values: Map<*, *>,
1577
- fullKey: String,
1578
- compactKey: String,
1579
- ): Int? = ((values[fullKey] ?: values[compactKey]) as? Number)?.toInt()
1580
-
1581
1596
  private fun numberValue(
1582
1597
  values: Map<String, Any>,
1583
1598
  vararg keys: String,