@mentra/bluetooth-sdk 0.1.6 → 0.1.8

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 (110) hide show
  1. package/README.md +26 -22
  2. package/android/.project +28 -0
  3. package/android/build.gradle +5 -9
  4. package/android/lc3Lib/.project +28 -0
  5. package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +54 -33
  6. package/android/src/main/java/com/mentra/bluetoothsdk/Bridge.kt +48 -10
  7. package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +27 -36
  8. package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +8 -4
  9. package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +170 -14
  10. package/android/src/main/java/com/mentra/bluetoothsdk/audio/AudioModels.kt +6 -6
  11. package/android/src/main/java/com/mentra/bluetoothsdk/camera/CameraModels.kt +69 -10
  12. package/android/src/main/java/com/mentra/bluetoothsdk/controllers/ControllerManager.kt +5 -3
  13. package/android/src/main/java/com/mentra/bluetoothsdk/controllers/R1.kt +2 -1
  14. package/android/src/main/java/com/mentra/bluetoothsdk/debug/BleTraceLogger.kt +149 -0
  15. package/android/src/main/java/com/mentra/bluetoothsdk/events/BluetoothEvents.kt +13 -0
  16. package/android/src/main/java/com/mentra/bluetoothsdk/photoreceiver/LocalPhotoUploadServer.kt +297 -0
  17. package/android/src/main/java/com/mentra/bluetoothsdk/photoreceiver/MentraPhotoReceiverModule.kt +120 -0
  18. package/android/src/main/java/com/mentra/bluetoothsdk/services/Foreground.kt +13 -0
  19. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G1.java +1 -1
  20. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G2.kt +3 -1
  21. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Mach1.java +1 -1
  22. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.java +269 -28
  23. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraNex.kt +1 -1
  24. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/SGCManager.kt +8 -3
  25. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Simulated.kt +4 -1
  26. package/android/src/main/java/com/mentra/bluetoothsdk/status/DeviceStatus.kt +14 -12
  27. package/android/src/main/java/com/mentra/bluetoothsdk/status/RuntimeState.kt +4 -2
  28. package/android/src/main/java/com/mentra/bluetoothsdk/streaming/StreamModels.kt +131 -6
  29. package/android/src/main/java/com/mentra/bluetoothsdk/stt/SherpaOnnxTranscriber.kt +1 -3
  30. package/android/src/main/java/com/mentra/bluetoothsdk/utils/MessageChunkReassembler.java +105 -0
  31. package/build/BluetoothSdk.types.d.ts +107 -48
  32. package/build/BluetoothSdk.types.d.ts.map +1 -1
  33. package/build/BluetoothSdk.types.js +4 -0
  34. package/build/BluetoothSdk.types.js.map +1 -1
  35. package/build/_private/BluetoothSdkModule.d.ts +8 -6
  36. package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
  37. package/build/_private/BluetoothSdkModule.js +58 -9
  38. package/build/_private/BluetoothSdkModule.js.map +1 -1
  39. package/build/index.d.ts +2 -2
  40. package/build/index.d.ts.map +1 -1
  41. package/build/index.js +6 -5
  42. package/build/index.js.map +1 -1
  43. package/build/photo-receiver/MentraPhotoReceiver.types.d.ts +18 -0
  44. package/build/photo-receiver/MentraPhotoReceiver.types.d.ts.map +1 -0
  45. package/build/photo-receiver/MentraPhotoReceiver.types.js +2 -0
  46. package/build/photo-receiver/MentraPhotoReceiver.types.js.map +1 -0
  47. package/build/photo-receiver/MentraPhotoReceiverModule.d.ts +10 -0
  48. package/build/photo-receiver/MentraPhotoReceiverModule.d.ts.map +1 -0
  49. package/build/photo-receiver/MentraPhotoReceiverModule.js +3 -0
  50. package/build/photo-receiver/MentraPhotoReceiverModule.js.map +1 -0
  51. package/build/photo-receiver/MentraPhotoReceiverModule.web.d.ts +10 -0
  52. package/build/photo-receiver/MentraPhotoReceiverModule.web.d.ts.map +1 -0
  53. package/build/photo-receiver/MentraPhotoReceiverModule.web.js +12 -0
  54. package/build/photo-receiver/MentraPhotoReceiverModule.web.js.map +1 -0
  55. package/build/photo-receiver/index.d.ts +3 -0
  56. package/build/photo-receiver/index.d.ts.map +1 -0
  57. package/build/photo-receiver/index.js +3 -0
  58. package/build/photo-receiver/index.js.map +1 -0
  59. package/build/react/useMentraBluetooth.d.ts +5 -3
  60. package/build/react/useMentraBluetooth.d.ts.map +1 -1
  61. package/build/react/useMentraBluetooth.js +15 -4
  62. package/build/react/useMentraBluetooth.js.map +1 -1
  63. package/expo-module.config.json +5 -2
  64. package/ios/BluetoothSdkModule.swift +59 -26
  65. package/ios/LocalPhotoUploadServer.swift +411 -0
  66. package/ios/MentraBluetoothSDK.podspec +8 -5
  67. package/ios/MentraPhotoReceiverModule.swift +147 -0
  68. package/ios/Source/Audio/AudioModels.swift +10 -10
  69. package/ios/Source/Bridge.swift +42 -12
  70. package/ios/Source/Camera/CameraModels.swift +74 -19
  71. package/ios/Source/DeviceManager.swift +23 -98
  72. package/ios/Source/DeviceStore.swift +12 -4
  73. package/ios/Source/Events/BluetoothEvents.swift +37 -0
  74. package/ios/Source/MentraBluetoothSDK.swift +135 -14
  75. package/ios/Source/Status/DeviceStatus.swift +12 -4
  76. package/ios/Source/Status/RuntimeState.swift +22 -13
  77. package/ios/Source/Streaming/StreamModels.swift +239 -45
  78. package/ios/Source/controllers/ControllerManager.swift +3 -3
  79. package/ios/Source/controllers/R1.swift +9 -3
  80. package/ios/Source/sgcs/G1.swift +5 -4
  81. package/ios/Source/sgcs/G2.swift +5 -6
  82. package/ios/Source/sgcs/Mach1.swift +1 -1
  83. package/ios/Source/sgcs/MentraLive.swift +183 -22
  84. package/ios/Source/sgcs/MentraNex.swift +1 -1
  85. package/ios/Source/sgcs/SGCManager.swift +11 -3
  86. package/ios/Source/sgcs/Simulated.swift +2 -2
  87. package/ios/Source/stt/SherpaOnnxTranscriber.swift +1 -4
  88. package/ios/Source/utils/MessageChunkReassembler.swift +89 -0
  89. package/package.json +6 -1
  90. package/src/BluetoothSdk.types.ts +133 -62
  91. package/src/_private/BluetoothSdkModule.ts +79 -26
  92. package/src/index.ts +15 -8
  93. package/src/photo-receiver/MentraPhotoReceiver.types.ts +20 -0
  94. package/src/photo-receiver/MentraPhotoReceiverModule.ts +14 -0
  95. package/src/photo-receiver/MentraPhotoReceiverModule.web.ts +20 -0
  96. package/src/photo-receiver/index.ts +2 -0
  97. package/src/react/useMentraBluetooth.ts +19 -7
  98. package/android/src/main/java/com/mentra/bluetoothsdk/stt/VadGateSpeechPolicy.kt +0 -221
  99. package/ios/Packages/VAD/Common/VAD/VADQuality.swift +0 -41
  100. package/ios/Packages/VAD/Common/VAD/VADState.swift +0 -26
  101. package/ios/Packages/VAD/Common/VAD/VADStrategy.swift +0 -29
  102. package/ios/Packages/VAD/Common/VAD/VADType.swift +0 -14
  103. package/ios/Packages/VAD/Data/Configuration.swift +0 -68
  104. package/ios/Packages/VAD/Data/FrameSize.swift +0 -39
  105. package/ios/Packages/VAD/Data/Record.swift +0 -13
  106. package/ios/Packages/VAD/Data/Result.swift +0 -22
  107. package/ios/Packages/VAD/Data/SampleRate.swift +0 -48
  108. package/ios/Packages/VAD/Silero/Model/silero_vad.onnx +0 -0
  109. package/ios/Packages/VAD/Silero/SileroVAD.swift +0 -284
  110. package/ios/Packages/VAD/Silero/SileroVADStrategy.swift +0 -64
@@ -1,6 +1,20 @@
1
1
  import CoreBluetooth
2
2
  import Foundation
3
3
 
4
+ @MainActor
5
+ private final class ActiveStreamKeepAlive {
6
+ let streamId: String
7
+ let intervalSeconds: Int
8
+ var pendingAckId: String?
9
+ var missedAckCount = 0
10
+ var task: Task<Void, Never>?
11
+
12
+ init(streamId: String, intervalSeconds: Int) {
13
+ self.streamId = streamId
14
+ self.intervalSeconds = intervalSeconds
15
+ }
16
+ }
17
+
4
18
  @MainActor
5
19
  private final class ActiveScanSession {
6
20
  let model: DeviceModel
@@ -33,6 +47,7 @@ public final class MentraBluetoothSDK {
33
47
  private var suppressDefaultDeviceEvents = false
34
48
  private var defaultDeviceApplyGeneration = 0
35
49
  private var activeScanSessions: [UUID: ActiveScanSession] = [:]
50
+ private var activeStreamKeepAlive: ActiveStreamKeepAlive?
36
51
 
37
52
  public init(configuration: MentraBluetoothSDKConfiguration = .default) {
38
53
  self.configuration = configuration
@@ -268,8 +283,12 @@ public final class MentraBluetoothSDK {
268
283
  DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "screen_disabled", disabled)
269
284
  }
270
285
 
271
- public func setGalleryMode(_ mode: GalleryMode) async throws {
272
- DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "galleryModeAuto", mode == .auto)
286
+ public func setGalleryModeEnabled(_ enabled: Bool) async throws {
287
+ DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "gallery_mode", enabled)
288
+ }
289
+
290
+ public func setVoiceActivityDetectionEnabled(_ enabled: Bool) async throws {
291
+ DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "voice_activity_detection_enabled", enabled)
273
292
  }
274
293
 
275
294
  public func setButtonPhotoSettings(size: ButtonPhotoSize) async throws {
@@ -280,14 +299,14 @@ public final class MentraBluetoothSDK {
280
299
  try await setButtonPhotoSettings(size: settings.size)
281
300
  }
282
301
 
283
- public func setButtonVideoRecordingSettings(width: Int, height: Int, frameRate: Int) async throws {
302
+ public func setButtonVideoRecordingSettings(width: Int, height: Int, fps: Int) async throws {
284
303
  DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "button_video_width", width)
285
304
  DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "button_video_height", height)
286
- DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "button_video_fps", frameRate)
305
+ DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "button_video_fps", fps)
287
306
  }
288
307
 
289
308
  public func setButtonVideoRecordingSettings(_ settings: ButtonVideoRecordingSettings) async throws {
290
- try await setButtonVideoRecordingSettings(width: settings.width, height: settings.height, frameRate: settings.frameRate)
309
+ try await setButtonVideoRecordingSettings(width: settings.width, height: settings.height, fps: settings.fps)
291
310
  }
292
311
 
293
312
  public func setButtonCameraLed(enabled: Bool) async throws {
@@ -305,7 +324,6 @@ public final class MentraBluetoothSDK {
305
324
  public func setMicState(
306
325
  enabled: Bool,
307
326
  useGlassesMic: Bool = true,
308
- bypassVad: Bool = true,
309
327
  sendTranscript: Bool = false,
310
328
  sendLc3Data: Bool = false
311
329
  ) {
@@ -319,7 +337,6 @@ public final class MentraBluetoothSDK {
319
337
  applyMicState(
320
338
  sendPcmData: enabled,
321
339
  sendTranscript: enabled && sendTranscript,
322
- bypassVad: bypassVad,
323
340
  sendLc3Data: enabled && sendLc3Data
324
341
  )
325
342
  }
@@ -327,13 +344,11 @@ public final class MentraBluetoothSDK {
327
344
  private func applyMicState(
328
345
  sendPcmData: Bool,
329
346
  sendTranscript: Bool,
330
- bypassVad: Bool,
331
347
  sendLc3Data: Bool
332
348
  ) {
333
349
  DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "should_send_pcm", sendPcmData)
334
350
  DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "should_send_lc3", sendLc3Data)
335
351
  DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "should_send_transcript", sendTranscript)
336
- DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "bypass_vad", bypassVad)
337
352
  DeviceManager.shared.setMicState()
338
353
  }
339
354
 
@@ -376,6 +391,9 @@ public final class MentraBluetoothSDK {
376
391
  }
377
392
 
378
393
  public func requestPhoto(_ request: PhotoRequest) {
394
+ Bridge.log(
395
+ "NATIVE: PHOTO PIPELINE [3b/6] MentraBluetoothSdk.requestPhoto requestId=\(request.requestId) appId=\(request.appId)"
396
+ )
379
397
  DeviceManager.shared.requestPhoto(
380
398
  request.requestId,
381
399
  request.appId,
@@ -383,7 +401,10 @@ public final class MentraBluetoothSDK {
383
401
  request.webhookUrl,
384
402
  request.authToken,
385
403
  request.compress?.rawValue,
386
- request.sound
404
+ request.flash,
405
+ request.sound,
406
+ exposureTimeNs: request.exposureTimeNs,
407
+ iso: request.iso
387
408
  )
388
409
  }
389
410
 
@@ -392,10 +413,17 @@ public final class MentraBluetoothSDK {
392
413
  }
393
414
 
394
415
  public func startStream(_ request: StreamRequest) {
395
- DeviceManager.shared.startStream(request.values)
416
+ var values = request.values
417
+ let streamId = stringValue(values, "streamId").flatMap { $0.isEmpty ? nil : $0 } ?? "sdk-\(UUID().uuidString)"
418
+ values["streamId"] = streamId
419
+ stopStreamKeepAliveMonitor()
420
+ DeviceManager.shared.startStream(values)
421
+ if request.keepAlive, !request.isExternallyManagedKeepAlive {
422
+ startStreamKeepAliveMonitor(streamId: streamId, intervalSeconds: request.keepAliveIntervalSeconds)
423
+ }
396
424
  }
397
425
 
398
- public func keepStreamAlive(_ request: StreamKeepAliveRequest) {
426
+ func sendCloudStreamKeepAlive(_ request: StreamKeepAliveRequest) {
399
427
  DeviceManager.shared.keepStreamAlive(request.values)
400
428
  }
401
429
 
@@ -412,6 +440,7 @@ public final class MentraBluetoothSDK {
412
440
  }
413
441
 
414
442
  public func stopStream() {
443
+ stopStreamKeepAliveMonitor()
415
444
  DeviceManager.shared.stopStream()
416
445
  }
417
446
 
@@ -452,6 +481,7 @@ public final class MentraBluetoothSDK {
452
481
  }
453
482
 
454
483
  public func invalidate() {
484
+ stopStreamKeepAliveMonitor()
455
485
  if let bridgeEventSinkId {
456
486
  Bridge.removeEventSink(bridgeEventSinkId)
457
487
  self.bridgeEventSinkId = nil
@@ -463,6 +493,80 @@ public final class MentraBluetoothSDK {
463
493
  delegate = nil
464
494
  }
465
495
 
496
+ private func startStreamKeepAliveMonitor(streamId: String, intervalSeconds requestedIntervalSeconds: Int) {
497
+ let intervalSeconds = requestedIntervalSeconds > 0 ? requestedIntervalSeconds : 5
498
+ let tracker = ActiveStreamKeepAlive(streamId: streamId, intervalSeconds: intervalSeconds)
499
+ activeStreamKeepAlive = tracker
500
+ sendNextStreamKeepAlive(for: tracker)
501
+ }
502
+
503
+ private func stopStreamKeepAliveMonitor() {
504
+ activeStreamKeepAlive?.task?.cancel()
505
+ activeStreamKeepAlive = nil
506
+ }
507
+
508
+ private func sendNextStreamKeepAlive(for tracker: ActiveStreamKeepAlive) {
509
+ guard activeStreamKeepAlive === tracker else { return }
510
+
511
+ if tracker.pendingAckId != nil {
512
+ tracker.missedAckCount += 1
513
+ if tracker.missedAckCount >= 3 {
514
+ activeStreamKeepAlive = nil
515
+ tracker.task?.cancel()
516
+ let event = StreamStatusEvent(
517
+ status: .error(
518
+ streamId: tracker.streamId,
519
+ errorDetails: "Stream keep-alive timed out after \(tracker.missedAckCount) missed ACKs",
520
+ timestamp: Int(Date().timeIntervalSince1970 * 1000),
521
+ resolvedConfig: nil
522
+ )
523
+ )
524
+ delegate?.mentraBluetoothSDK(self, didReceive: .streamStatus(event))
525
+ return
526
+ }
527
+ }
528
+
529
+ let ackId = "ack-\(Int(Date().timeIntervalSince1970 * 1000))"
530
+ tracker.pendingAckId = ackId
531
+ DeviceManager.shared.keepStreamAlive(
532
+ StreamKeepAliveRequest(streamId: tracker.streamId, ackId: ackId).values
533
+ )
534
+
535
+ tracker.task?.cancel()
536
+ tracker.task = Task { @MainActor [weak self, weak tracker] in
537
+ guard let tracker else { return }
538
+ try? await Task.sleep(nanoseconds: UInt64(tracker.intervalSeconds) * 1_000_000_000)
539
+ self?.sendNextStreamKeepAlive(for: tracker)
540
+ }
541
+ }
542
+
543
+ private func handleStreamKeepAliveAck(_ event: KeepAliveAckEvent) -> Bool {
544
+ guard let tracker = activeStreamKeepAlive,
545
+ event.streamId == tracker.streamId,
546
+ event.ackId == tracker.pendingAckId
547
+ else {
548
+ return false
549
+ }
550
+ tracker.pendingAckId = nil
551
+ tracker.missedAckCount = 0
552
+ return true
553
+ }
554
+
555
+ private func handleStreamStatusForKeepAlive(_ status: StreamStatus) {
556
+ if let streamId = status.streamId,
557
+ activeStreamKeepAlive?.streamId != streamId
558
+ {
559
+ return
560
+ }
561
+
562
+ switch status.state {
563
+ case .stopped, .stopping, .error, .reconnectFailed:
564
+ stopStreamKeepAliveMonitor()
565
+ default:
566
+ break
567
+ }
568
+ }
569
+
466
570
  private func dispatchStoreUpdate(_ category: String, _ changes: [String: Any]) {
467
571
  switch ObservableStore.normalizeCategory(category) {
468
572
  case "glasses":
@@ -573,6 +677,16 @@ public final class MentraBluetoothSDK {
573
677
  values: data
574
678
  )
575
679
  delegate?.mentraBluetoothSDK(self, didReceive: .localTranscription(event))
680
+ case "voice_activity_detection_status":
681
+ delegate?.mentraBluetoothSDK(
682
+ self,
683
+ didReceive: .voiceActivityDetectionStatus(VoiceActivityDetectionStatusEvent(values: data))
684
+ )
685
+ case "speaking_status":
686
+ delegate?.mentraBluetoothSDK(
687
+ self,
688
+ didReceive: .speakingStatus(SpeakingStatusEvent(values: data))
689
+ )
576
690
  case "hotspot_status_change":
577
691
  delegate?.mentraBluetoothSDK(self, didReceive: .hotspotStatus(HotspotStatusEvent(values: data)))
578
692
  case "wifi_status_change":
@@ -581,10 +695,17 @@ public final class MentraBluetoothSDK {
581
695
  delegate?.mentraBluetoothSDK(self, didReceive: .hotspotError(HotspotErrorEvent(values: data)))
582
696
  case "photo_response":
583
697
  delegate?.mentraBluetoothSDK(self, didReceive: .photoResponse(PhotoResponseEvent(values: data)))
698
+ case "photo_status":
699
+ delegate?.mentraBluetoothSDK(self, didReceive: .photoStatus(PhotoStatusEvent(values: data)))
584
700
  case "stream_status":
585
- delegate?.mentraBluetoothSDK(self, didReceive: .streamStatus(StreamStatusEvent(values: data)))
701
+ let event = StreamStatusEvent(values: data)
702
+ handleStreamStatusForKeepAlive(event.status)
703
+ delegate?.mentraBluetoothSDK(self, didReceive: .streamStatus(event))
586
704
  case "keep_alive_ack":
587
- delegate?.mentraBluetoothSDK(self, didReceive: .keepAliveAck(KeepAliveAckEvent(values: data)))
705
+ let event = KeepAliveAckEvent(values: data)
706
+ if !handleStreamKeepAliveAck(event) {
707
+ delegate?.mentraBluetoothSDK(self, didReceive: .keepAliveAck(event))
708
+ }
588
709
  case "compatible_glasses_search_stop":
589
710
  delegate?.mentraBluetoothSDK(self, didStopScan: .completed)
590
711
  case "pair_failure":
@@ -133,6 +133,7 @@ struct GlassesStatus: CustomStringConvertible {
133
133
  var fullyBooted: Bool { boolValue(values, "fullyBooted") ?? false }
134
134
  var connected: Bool { boolValue(values, "connected") ?? false }
135
135
  var micEnabled: Bool { boolValue(values, "micEnabled") ?? false }
136
+ var voiceActivityDetectionEnabled: Bool { boolValue(values, "voiceActivityDetectionEnabled") ?? true }
136
137
  var connectionState: GlassesConnectionState { GlassesConnectionState(stringValue(values, "connectionState")) }
137
138
  var bluetoothClassicConnected: Bool { boolValue(values, "bluetoothClassicConnected") ?? false }
138
139
  var signalStrength: Int { intValue(values["signalStrength"]) ?? -1 }
@@ -246,6 +247,10 @@ struct BluetoothStatus: CustomStringConvertible {
246
247
  if let searchResults = values["searchResults"] as? [[String: Any]] {
247
248
  normalizedValues["searchResults"] = searchResults.compactMap { Device(values: $0)?.dictionary }
248
249
  }
250
+ if let galleryMode = boolValue(values, "gallery_mode") {
251
+ normalizedValues["galleryModeEnabled"] = galleryMode
252
+ normalizedValues.removeValue(forKey: "gallery_mode")
253
+ }
249
254
  return normalizedValues
250
255
  }
251
256
 
@@ -294,7 +299,7 @@ struct BluetoothStatus: CustomStringConvertible {
294
299
  var dashboardDepth: Int { intValue(values["dashboard_depth"]) ?? 2 }
295
300
  var headUpAngle: Int { intValue(values["head_up_angle"]) ?? 30 }
296
301
  var contextualDashboard: Bool { boolValue(values, "contextual_dashboard") ?? true }
297
- var galleryModeAuto: Bool { boolValue(values, "galleryModeAuto") ?? true }
302
+ var galleryModeEnabled: Bool { boolValue(values, "gallery_mode") ?? boolValue(values, "galleryModeEnabled") ?? true }
298
303
  var buttonPhotoSize: ButtonPhotoSize {
299
304
  ButtonPhotoSize(rawValue: stringValue(values, "button_photo_size") ?? "") ?? .medium
300
305
  }
@@ -306,7 +311,6 @@ struct BluetoothStatus: CustomStringConvertible {
306
311
  var shouldSendPcm: Bool { boolValue(values, "should_send_pcm") ?? false }
307
312
  var shouldSendLc3: Bool { boolValue(values, "should_send_lc3") ?? false }
308
313
  var shouldSendTranscript: Bool { boolValue(values, "should_send_transcript") ?? false }
309
- var bypassVad: Bool { boolValue(values, "bypass_vad") ?? true }
310
314
  var offlineCaptionsRunning: Bool { boolValue(values, "offline_captions_running") ?? false }
311
315
  var localSttFallbackActive: Bool { boolValue(values, "local_stt_fallback_active") ?? false }
312
316
  var shouldSendBootingMessage: Bool { boolValue(values, "shouldSendBootingMessage") ?? true }
@@ -335,6 +339,7 @@ struct GlassesStatusUpdate: CustomStringConvertible {
335
339
  var fullyBooted: Bool? { optionalBoolValue(values, "fullyBooted") }
336
340
  var connected: Bool? { optionalBoolValue(values, "connected") }
337
341
  var micEnabled: Bool? { optionalBoolValue(values, "micEnabled") }
342
+ var voiceActivityDetectionEnabled: Bool? { optionalBoolValue(values, "voiceActivityDetectionEnabled") }
338
343
  var connectionState: GlassesConnectionState? {
339
344
  GlassesConnectionState.fromValue(optionalStringValue(values, "connectionState"))
340
345
  }
@@ -403,6 +408,10 @@ struct BluetoothStatusUpdate: CustomStringConvertible {
403
408
  if let searchResults = values["searchResults"] as? [[String: Any]] {
404
409
  normalizedValues["searchResults"] = searchResults.compactMap { Device(values: $0)?.dictionary }
405
410
  }
411
+ if let galleryMode = optionalBoolValue(values, "gallery_mode") {
412
+ normalizedValues["galleryModeEnabled"] = galleryMode
413
+ normalizedValues.removeValue(forKey: "gallery_mode")
414
+ }
406
415
  self.values = normalizedValues
407
416
  }
408
417
 
@@ -440,7 +449,7 @@ struct BluetoothStatusUpdate: CustomStringConvertible {
440
449
  var dashboardDepth: Int? { optionalIntValue(values, "dashboard_depth") }
441
450
  var headUpAngle: Int? { optionalIntValue(values, "head_up_angle") }
442
451
  var contextualDashboard: Bool? { optionalBoolValue(values, "contextual_dashboard") }
443
- var galleryModeAuto: Bool? { optionalBoolValue(values, "galleryModeAuto") }
452
+ var galleryModeEnabled: Bool? { optionalBoolValue(values, "gallery_mode") ?? optionalBoolValue(values, "galleryModeEnabled") }
444
453
  var buttonPhotoSize: ButtonPhotoSize? {
445
454
  optionalStringValue(values, "button_photo_size").flatMap(ButtonPhotoSize.init(rawValue:))
446
455
  }
@@ -452,7 +461,6 @@ struct BluetoothStatusUpdate: CustomStringConvertible {
452
461
  var shouldSendPcm: Bool? { optionalBoolValue(values, "should_send_pcm") }
453
462
  var shouldSendLc3: Bool? { optionalBoolValue(values, "should_send_lc3") }
454
463
  var shouldSendTranscript: Bool? { optionalBoolValue(values, "should_send_transcript") }
455
- var bypassVad: Bool? { optionalBoolValue(values, "bypass_vad") }
456
464
  var offlineCaptionsRunning: Bool? { optionalBoolValue(values, "offline_captions_running") }
457
465
  var localSttFallbackActive: Bool? { optionalBoolValue(values, "local_stt_fallback_active") }
458
466
  var shouldSendBootingMessage: Bool? { optionalBoolValue(values, "shouldSendBootingMessage") }
@@ -121,6 +121,7 @@ public enum GlassesRuntimeState: CustomStringConvertible {
121
121
  hotspot: HotspotStatus,
122
122
  ready: Bool,
123
123
  signal: SignalState,
124
+ voiceActivityDetectionEnabled: Bool,
124
125
  wifi: WifiStatus
125
126
  )
126
127
 
@@ -153,6 +154,7 @@ public enum GlassesRuntimeState: CustomStringConvertible {
153
154
  strengthDbm: status.signalStrength == -1 ? nil : status.signalStrength,
154
155
  updatedAt: status.signalStrengthUpdatedAt <= 0 ? nil : status.signalStrengthUpdatedAt
155
156
  ),
157
+ voiceActivityDetectionEnabled: status.voiceActivityDetectionEnabled,
156
158
  wifi: status.wifi
157
159
  )
158
160
  }
@@ -168,7 +170,7 @@ public enum GlassesRuntimeState: CustomStringConvertible {
168
170
  switch self {
169
171
  case let .disconnected(connection):
170
172
  connection
171
- case let .connected(_, connection, _, _, _, _, _, _):
173
+ case let .connected(_, connection, _, _, _, _, _, _, _):
172
174
  connection
173
175
  }
174
176
  }
@@ -177,48 +179,55 @@ public enum GlassesRuntimeState: CustomStringConvertible {
177
179
  switch self {
178
180
  case .disconnected:
179
181
  false
180
- case let .connected(_, _, _, _, _, ready, _, _):
182
+ case let .connected(_, _, _, _, _, ready, _, _, _):
181
183
  ready
182
184
  }
183
185
  }
184
186
 
185
187
  public var battery: GlassesBatteryState? {
186
- guard case let .connected(battery, _, _, _, _, _, _, _) = self else {
188
+ guard case let .connected(battery, _, _, _, _, _, _, _, _) = self else {
187
189
  return nil
188
190
  }
189
191
  return battery
190
192
  }
191
193
 
192
194
  public var device: ConnectedGlassesInfo? {
193
- guard case let .connected(_, _, device, _, _, _, _, _) = self else {
195
+ guard case let .connected(_, _, device, _, _, _, _, _, _) = self else {
194
196
  return nil
195
197
  }
196
198
  return device
197
199
  }
198
200
 
199
201
  public var firmware: FirmwareInfo? {
200
- guard case let .connected(_, _, _, firmware, _, _, _, _) = self else {
202
+ guard case let .connected(_, _, _, firmware, _, _, _, _, _) = self else {
201
203
  return nil
202
204
  }
203
205
  return firmware
204
206
  }
205
207
 
206
208
  public var hotspot: HotspotStatus? {
207
- guard case let .connected(_, _, _, _, hotspot, _, _, _) = self else {
209
+ guard case let .connected(_, _, _, _, hotspot, _, _, _, _) = self else {
208
210
  return nil
209
211
  }
210
212
  return hotspot
211
213
  }
212
214
 
213
215
  public var signal: SignalState? {
214
- guard case let .connected(_, _, _, _, _, _, signal, _) = self else {
216
+ guard case let .connected(_, _, _, _, _, _, signal, _, _) = self else {
215
217
  return nil
216
218
  }
217
219
  return signal
218
220
  }
219
221
 
222
+ public var voiceActivityDetectionEnabled: Bool {
223
+ guard case let .connected(_, _, _, _, _, _, _, voiceActivityDetectionEnabled, _) = self else {
224
+ return false
225
+ }
226
+ return voiceActivityDetectionEnabled
227
+ }
228
+
220
229
  public var wifi: WifiStatus? {
221
- guard case let .connected(_, _, _, _, _, _, _, wifi) = self else {
230
+ guard case let .connected(_, _, _, _, _, _, _, _, wifi) = self else {
222
231
  return nil
223
232
  }
224
233
  return wifi
@@ -228,17 +237,17 @@ public enum GlassesRuntimeState: CustomStringConvertible {
228
237
  switch self {
229
238
  case let .disconnected(connection):
230
239
  "GlassesRuntimeState(\(connection.rawValue))"
231
- case let .connected(_, _, device, _, _, ready, _, _):
240
+ case let .connected(_, _, device, _, _, ready, _, _, _):
232
241
  "GlassesRuntimeState(connected: \(device), ready: \(ready))"
233
242
  }
234
243
  }
235
244
  }
236
245
 
237
246
  public struct GalleryModeState: Equatable {
238
- public let desired: GalleryMode
247
+ public let enabled: Bool
239
248
 
240
- public init(desired: GalleryMode) {
241
- self.desired = desired
249
+ public init(enabled: Bool) {
250
+ self.enabled = enabled
242
251
  }
243
252
  }
244
253
 
@@ -288,7 +297,7 @@ public struct PhoneSdkRuntimeState: CustomStringConvertible {
288
297
  init(status: BluetoothStatus) {
289
298
  currentMic = MicMode(rawValue: status.currentMic)
290
299
  defaultDevice = status.defaultDevice
291
- galleryMode = GalleryModeState(desired: status.galleryModeAuto ? .auto : .manual)
300
+ galleryMode = GalleryModeState(enabled: status.galleryModeEnabled)
292
301
  lastLog = status.lastLog
293
302
  micRanking = status.micRanking.compactMap(MicMode.init(rawValue:))
294
303
  otherBluetoothConnected = status.otherBtConnected