@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
@@ -197,10 +197,10 @@ public struct Device: Identifiable, Equatable, CustomStringConvertible {
197
197
  }
198
198
 
199
199
  init?(values: [String: Any]) {
200
- guard let model = stringValue(values, "model", "deviceModel", "device_model") else { return nil }
201
- guard let name = stringValue(values, "name", "deviceName", "device_name") else { return nil }
202
- let identifier = stringValue(values, "address", "deviceAddress", "device_address").flatMap { $0.isEmpty ? nil : $0 }
203
- let rssi = intValue(values["rssi"]) ?? intValue(values["signalStrength"]) ?? intValue(values["signal_strength"])
200
+ guard let model = stringValue(values, "model") else { return nil }
201
+ guard let name = stringValue(values, "name") else { return nil }
202
+ let identifier = stringValue(values, "address").flatMap { $0.isEmpty ? nil : $0 }
203
+ let rssi = intValue(values["rssi"])
204
204
  self.init(
205
205
  model: DeviceModel.fromDeviceType(model),
206
206
  name: name,
@@ -236,8 +236,8 @@ public struct WifiScanResult: CustomStringConvertible {
236
236
 
237
237
  init(values: [String: Any]) {
238
238
  ssid = stringValue(values, "ssid") ?? ""
239
- requiresPassword = boolValue(values, "requiresPassword", "requires_password", "auth_required") ?? false
240
- signalStrength = intValue(values["signalStrength"] ?? values["signal_strength"] ?? values["rssi"]) ?? -1
239
+ requiresPassword = boolValue(values, "requiresPassword") ?? false
240
+ signalStrength = intValue(values["signalStrength"]) ?? -1
241
241
  frequency = intValue(values["frequency"])
242
242
  }
243
243
 
@@ -355,15 +355,15 @@ public struct GlassesStatus: CustomStringConvertible {
355
355
  public var connected: Bool { boolValue(values, "connected") ?? false }
356
356
  public var micEnabled: Bool { boolValue(values, "micEnabled") ?? false }
357
357
  public var connectionState: GlassesConnectionState { GlassesConnectionState(stringValue(values, "connectionState")) }
358
- public var btcConnected: Bool { boolValue(values, "btcConnected") ?? false }
358
+ public var bluetoothClassicConnected: Bool { boolValue(values, "bluetoothClassicConnected") ?? false }
359
359
  public var signalStrength: Int { intValue(values["signalStrength"]) ?? -1 }
360
360
  public var signalStrengthUpdatedAt: Int { intValue(values["signalStrengthUpdatedAt"]) ?? 0 }
361
361
  public var deviceModel: String { stringValue(values, "deviceModel") ?? "" }
362
362
  public var androidVersion: String { stringValue(values, "androidVersion") ?? "" }
363
- public var firmwareVersion: String { stringValue(values, "firmwareVersion", "fwVersion") ?? "" }
364
- public var besFirmwareVersion: String { stringValue(values, "besFwVersion", "besFirmwareVersion") ?? "" }
365
- public var mtkFirmwareVersion: String { stringValue(values, "mtkFwVersion", "mtkFirmwareVersion") ?? "" }
366
- public var btMacAddress: String { stringValue(values, "btMacAddress") ?? "" }
363
+ public var firmwareVersion: String { stringValue(values, "firmwareVersion") ?? "" }
364
+ public var besFirmwareVersion: String { stringValue(values, "besFirmwareVersion") ?? "" }
365
+ public var mtkFirmwareVersion: String { stringValue(values, "mtkFirmwareVersion") ?? "" }
366
+ public var bluetoothMacAddress: String { stringValue(values, "bluetoothMacAddress") ?? "" }
367
367
  public var leftMacAddress: String { stringValue(values, "leftMacAddress") ?? "" }
368
368
  public var rightMacAddress: String { stringValue(values, "rightMacAddress") ?? "" }
369
369
  public var macAddress: String { stringValue(values, "macAddress") ?? "" }
@@ -413,7 +413,6 @@ public struct GlassesStatus: CustomStringConvertible {
413
413
  dictionary.removeValue(forKey: "hotspotSsid")
414
414
  dictionary.removeValue(forKey: "hotspotPassword")
415
415
  dictionary.removeValue(forKey: "hotspotGatewayIp")
416
- dictionary.removeValue(forKey: "hotspotLocalIp")
417
416
  return dictionary
418
417
  }
419
418
 
@@ -443,7 +442,7 @@ public struct GlassesStatus: CustomStringConvertible {
443
442
  if let hotspot = (values["hotspot"] as? [String: Any]).flatMap(HotspotStatus.init(values:)) {
444
443
  dictionary["hotspot"] = hotspot.values
445
444
  }
446
- } else if hasAnyKey(values, "hotspotEnabled", "hotspotSsid", "hotspotPassword", "hotspotGatewayIp", "hotspotLocalIp") {
445
+ } else if hasAnyKey(values, "hotspotEnabled", "hotspotSsid", "hotspotPassword", "hotspotGatewayIp") {
447
446
  if let hotspot = HotspotStatus.fromStoreValues(values) {
448
447
  dictionary["hotspot"] = hotspot.values
449
448
  }
@@ -451,7 +450,6 @@ public struct GlassesStatus: CustomStringConvertible {
451
450
  dictionary.removeValue(forKey: "hotspotSsid")
452
451
  dictionary.removeValue(forKey: "hotspotPassword")
453
452
  dictionary.removeValue(forKey: "hotspotGatewayIp")
454
- dictionary.removeValue(forKey: "hotspotLocalIp")
455
453
  }
456
454
  return dictionary
457
455
  }
@@ -490,6 +488,8 @@ public struct BluetoothStatus: CustomStringConvertible {
490
488
  public var micEnabled: Bool { boolValue(values, "micEnabled") ?? false }
491
489
  public var currentMic: String { stringValue(values, "currentMic") ?? "" }
492
490
  public var micRanking: [String] { stringListValue(values, "micRanking") }
491
+ /// Nearby glasses in stable discovery order. Existing entries keep their array position as
492
+ /// details refresh; new glasses append at the end, and removals should not reorder remaining entries.
493
493
  public var searchResults: [Device] {
494
494
  dictionaryListValue(values, "searchResults").compactMap(Device.init(values:))
495
495
  }
@@ -515,7 +515,7 @@ public struct BluetoothStatus: CustomStringConvertible {
515
515
  public var dashboardDepth: Int { intValue(values["dashboard_depth"]) ?? 2 }
516
516
  public var headUpAngle: Int { intValue(values["head_up_angle"]) ?? 30 }
517
517
  public var contextualDashboard: Bool { boolValue(values, "contextual_dashboard") ?? true }
518
- public var galleryModeAuto: Bool { boolValue(values, "gallery_mode") ?? true }
518
+ public var galleryModeAuto: Bool { boolValue(values, "galleryModeAuto") ?? true }
519
519
  public var buttonPhotoSize: ButtonPhotoSize {
520
520
  ButtonPhotoSize(rawValue: stringValue(values, "button_photo_size") ?? "") ?? .medium
521
521
  }
@@ -523,7 +523,7 @@ public struct BluetoothStatus: CustomStringConvertible {
523
523
  public var buttonMaxRecordingTime: Int { intValue(values["button_max_recording_time"]) ?? 10 }
524
524
  public var buttonVideoWidth: Int { intValue(values["button_video_width"]) ?? 1280 }
525
525
  public var buttonVideoHeight: Int { intValue(values["button_video_height"]) ?? 720 }
526
- public var buttonVideoFps: Int { intValue(values["button_video_fps"]) ?? 30 }
526
+ public var buttonVideoFrameRate: Int { intValue(values["button_video_fps"]) ?? 30 }
527
527
  public var shouldSendPcm: Bool { boolValue(values, "should_send_pcm") ?? false }
528
528
  public var shouldSendLc3: Bool { boolValue(values, "should_send_lc3") ?? false }
529
529
  public var shouldSendTranscript: Bool { boolValue(values, "should_send_transcript") ?? false }
@@ -559,15 +559,15 @@ public struct GlassesStatusUpdate: CustomStringConvertible {
559
559
  public var connectionState: GlassesConnectionState? {
560
560
  GlassesConnectionState.fromValue(optionalStringValue(values, "connectionState"))
561
561
  }
562
- public var btcConnected: Bool? { optionalBoolValue(values, "btcConnected") }
562
+ public var bluetoothClassicConnected: Bool? { optionalBoolValue(values, "bluetoothClassicConnected") }
563
563
  public var signalStrength: Int? { optionalIntValue(values, "signalStrength") }
564
564
  public var signalStrengthUpdatedAt: Int? { optionalIntValue(values, "signalStrengthUpdatedAt") }
565
565
  public var deviceModel: String? { optionalStringValue(values, "deviceModel") }
566
566
  public var androidVersion: String? { optionalStringValue(values, "androidVersion") }
567
- public var firmwareVersion: String? { optionalStringValue(values, "firmwareVersion", "fwVersion") }
568
- public var besFirmwareVersion: String? { optionalStringValue(values, "besFwVersion", "besFirmwareVersion") }
569
- public var mtkFirmwareVersion: String? { optionalStringValue(values, "mtkFwVersion", "mtkFirmwareVersion") }
570
- public var btMacAddress: String? { optionalStringValue(values, "btMacAddress") }
567
+ public var firmwareVersion: String? { optionalStringValue(values, "firmwareVersion") }
568
+ public var besFirmwareVersion: String? { optionalStringValue(values, "besFirmwareVersion") }
569
+ public var mtkFirmwareVersion: String? { optionalStringValue(values, "mtkFirmwareVersion") }
570
+ public var bluetoothMacAddress: String? { optionalStringValue(values, "bluetoothMacAddress") }
571
571
  public var leftMacAddress: String? { optionalStringValue(values, "leftMacAddress") }
572
572
  public var rightMacAddress: String? { optionalStringValue(values, "rightMacAddress") }
573
573
  public var macAddress: String? { optionalStringValue(values, "macAddress") }
@@ -591,7 +591,7 @@ public struct GlassesStatusUpdate: CustomStringConvertible {
591
591
  if let hotspot = values["hotspot"] as? [String: Any] {
592
592
  return HotspotStatus(values: hotspot)
593
593
  }
594
- if hasAnyKey(values, "hotspotEnabled", "hotspotSsid", "hotspotPassword", "hotspotGatewayIp", "hotspotLocalIp") {
594
+ if hasAnyKey(values, "hotspotEnabled", "hotspotSsid", "hotspotPassword", "hotspotGatewayIp") {
595
595
  return HotspotStatus.fromStoreValues(values)
596
596
  }
597
597
  return nil
@@ -633,6 +633,9 @@ public struct BluetoothStatusUpdate: CustomStringConvertible {
633
633
  public var micEnabled: Bool? { optionalBoolValue(values, "micEnabled") }
634
634
  public var currentMic: String? { optionalStringValue(values, "currentMic") }
635
635
  public var micRanking: [String]? { optionalStringListValue(values, "micRanking") }
636
+ /// Nearby glasses in stable discovery order when included in an update. Existing entries keep their
637
+ /// array position as details refresh; new glasses append at the end, and removals should not reorder
638
+ /// remaining entries.
636
639
  public var searchResults: [Device]? {
637
640
  optionalDictionaryListValue(values, "searchResults")?.compactMap(Device.init(values:))
638
641
  }
@@ -658,7 +661,7 @@ public struct BluetoothStatusUpdate: CustomStringConvertible {
658
661
  public var dashboardDepth: Int? { optionalIntValue(values, "dashboard_depth") }
659
662
  public var headUpAngle: Int? { optionalIntValue(values, "head_up_angle") }
660
663
  public var contextualDashboard: Bool? { optionalBoolValue(values, "contextual_dashboard") }
661
- public var galleryModeAuto: Bool? { optionalBoolValue(values, "gallery_mode") }
664
+ public var galleryModeAuto: Bool? { optionalBoolValue(values, "galleryModeAuto") }
662
665
  public var buttonPhotoSize: ButtonPhotoSize? {
663
666
  optionalStringValue(values, "button_photo_size").flatMap(ButtonPhotoSize.init(rawValue:))
664
667
  }
@@ -666,7 +669,7 @@ public struct BluetoothStatusUpdate: CustomStringConvertible {
666
669
  public var buttonMaxRecordingTime: Int? { optionalIntValue(values, "button_max_recording_time") }
667
670
  public var buttonVideoWidth: Int? { optionalIntValue(values, "button_video_width") }
668
671
  public var buttonVideoHeight: Int? { optionalIntValue(values, "button_video_height") }
669
- public var buttonVideoFps: Int? { optionalIntValue(values, "button_video_fps") }
672
+ public var buttonVideoFrameRate: Int? { optionalIntValue(values, "button_video_fps") }
670
673
  public var shouldSendPcm: Bool? { optionalBoolValue(values, "should_send_pcm") }
671
674
  public var shouldSendLc3: Bool? { optionalBoolValue(values, "should_send_lc3") }
672
675
  public var shouldSendTranscript: Bool? { optionalBoolValue(values, "should_send_transcript") }
@@ -703,10 +706,10 @@ public struct DisplayTextRequest {
703
706
  }
704
707
  }
705
708
 
706
- public struct DisplayEventRequest {
707
- public let values: [String: Any]
709
+ struct DisplayEventRequest {
710
+ let values: [String: Any]
708
711
 
709
- public init(values: [String: Any]) {
712
+ init(values: [String: Any]) {
710
713
  self.values = values
711
714
  }
712
715
  }
@@ -721,12 +724,12 @@ public struct DashboardPositionRequest {
721
724
  }
722
725
  }
723
726
 
724
- public struct DashboardMenuItem {
725
- public let title: String
726
- public let packageName: String
727
- public let values: [String: Any]
727
+ struct DashboardMenuItem {
728
+ let title: String
729
+ let packageName: String
730
+ let values: [String: Any]
728
731
 
729
- public init(title: String, packageName: String, values: [String: Any] = [:]) {
732
+ init(title: String, packageName: String, values: [String: Any] = [:]) {
730
733
  self.title = title
731
734
  self.packageName = packageName
732
735
  self.values = values
@@ -772,12 +775,12 @@ public struct ButtonPhotoSettings {
772
775
  public struct ButtonVideoRecordingSettings {
773
776
  public let width: Int
774
777
  public let height: Int
775
- public let fps: Int
778
+ public let frameRate: Int
776
779
 
777
- public init(width: Int, height: Int, fps: Int) {
780
+ public init(width: Int, height: Int, frameRate: Int) {
778
781
  self.width = width
779
782
  self.height = height
780
- self.fps = fps
783
+ self.frameRate = frameRate
781
784
  }
782
785
  }
783
786
 
@@ -788,32 +791,13 @@ public enum CameraFov {
788
791
  var value: [String: Int] {
789
792
  switch self {
790
793
  case .standard:
791
- ["fov": 118, "roi_position": 0]
794
+ ["fov": 118, "roiPosition": 0]
792
795
  case .wide:
793
- ["fov": 118, "roi_position": 0]
796
+ ["fov": 118, "roiPosition": 0]
794
797
  }
795
798
  }
796
799
  }
797
800
 
798
- public struct MicConfiguration {
799
- public let sendPcmData: Bool
800
- public let sendTranscript: Bool
801
- public let bypassVad: Bool
802
- public let sendLc3Data: Bool
803
-
804
- public init(
805
- sendPcmData: Bool,
806
- sendTranscript: Bool,
807
- bypassVad: Bool,
808
- sendLc3Data: Bool = false
809
- ) {
810
- self.sendPcmData = sendPcmData
811
- self.sendTranscript = sendTranscript
812
- self.bypassVad = bypassVad
813
- self.sendLc3Data = sendLc3Data
814
- }
815
- }
816
-
817
801
  public enum MicPreference: String {
818
802
  case auto
819
803
  case phone
@@ -828,7 +812,6 @@ public struct PhotoRequest {
828
812
  public let webhookUrl: String?
829
813
  public let authToken: String?
830
814
  public let compress: PhotoCompression?
831
- public let flash: Bool
832
815
  public let sound: Bool
833
816
 
834
817
  public init(
@@ -838,7 +821,6 @@ public struct PhotoRequest {
838
821
  webhookUrl: String? = nil,
839
822
  authToken: String? = nil,
840
823
  compress: PhotoCompression? = nil,
841
- flash: Bool,
842
824
  sound: Bool
843
825
  ) {
844
826
  self.requestId = requestId
@@ -847,7 +829,6 @@ public struct PhotoRequest {
847
829
  self.webhookUrl = webhookUrl
848
830
  self.authToken = authToken
849
831
  self.compress = compress
850
- self.flash = flash
851
832
  self.sound = sound
852
833
  }
853
834
  }
@@ -882,10 +863,10 @@ public struct StreamVideoConfig {
882
863
  init?(values: [String: Any]?) {
883
864
  guard let values else { return nil }
884
865
  self.init(
885
- width: intValue(values["width"] ?? values["w"]),
886
- height: intValue(values["height"] ?? values["h"]),
887
- bitrate: intValue(values["bitrate"] ?? values["br"]),
888
- frameRate: intValue(values["frameRate"] ?? values["fr"])
866
+ width: intValue(values["width"]),
867
+ height: intValue(values["height"]),
868
+ bitrate: intValue(values["bitrate"]),
869
+ frameRate: intValue(values["frameRate"])
889
870
  )
890
871
  }
891
872
  }
@@ -920,10 +901,10 @@ public struct StreamAudioConfig {
920
901
  init?(values: [String: Any]?) {
921
902
  guard let values else { return nil }
922
903
  self.init(
923
- bitrate: intValue(values["bitrate"] ?? values["br"]),
924
- sampleRate: intValue(values["sampleRate"] ?? values["sr"]),
925
- echoCancellation: values["echoCancellation"] as? Bool ?? values["ec"] as? Bool,
926
- noiseSuppression: values["noiseSuppression"] as? Bool ?? values["ns"] as? Bool
904
+ bitrate: intValue(values["bitrate"]),
905
+ sampleRate: intValue(values["sampleRate"]),
906
+ echoCancellation: values["echoCancellation"] as? Bool,
907
+ noiseSuppression: values["noiseSuppression"] as? Bool
927
908
  )
928
909
  }
929
910
  }
@@ -933,7 +914,6 @@ public struct StreamRequest {
933
914
  public let streamId: String
934
915
  public let keepAlive: Bool
935
916
  public let keepAliveIntervalSeconds: Int
936
- public let flash: Bool
937
917
  public let sound: Bool
938
918
  public let video: StreamVideoConfig?
939
919
  public let audio: StreamAudioConfig?
@@ -944,7 +924,6 @@ public struct StreamRequest {
944
924
  streamId: String = "",
945
925
  keepAlive: Bool = true,
946
926
  keepAliveIntervalSeconds: Int = 15,
947
- flash: Bool = true,
948
927
  sound: Bool = true,
949
928
  video: StreamVideoConfig? = nil,
950
929
  audio: StreamAudioConfig? = nil,
@@ -954,7 +933,6 @@ public struct StreamRequest {
954
933
  self.streamId = streamId
955
934
  self.keepAlive = keepAlive
956
935
  self.keepAliveIntervalSeconds = keepAliveIntervalSeconds
957
- self.flash = flash
958
936
  self.sound = sound
959
937
  self.video = video
960
938
  self.audio = audio
@@ -971,10 +949,9 @@ public struct StreamRequest {
971
949
  streamId: values["streamId"] as? String ?? "",
972
950
  keepAlive: values["keepAlive"] as? Bool ?? true,
973
951
  keepAliveIntervalSeconds: intValue(values["keepAliveIntervalSeconds"]) ?? 15,
974
- flash: values["flash"] as? Bool ?? true,
975
952
  sound: values["sound"] as? Bool ?? true,
976
- video: StreamVideoConfig(values: (values["video"] ?? values["v"]) as? [String: Any]),
977
- audio: StreamAudioConfig(values: (values["audio"] ?? values["a"]) as? [String: Any]),
953
+ video: StreamVideoConfig(values: values["video"] as? [String: Any]),
954
+ audio: StreamAudioConfig(values: values["audio"] as? [String: Any]),
978
955
  extraValues: values
979
956
  )
980
957
  }
@@ -986,7 +963,8 @@ public struct StreamRequest {
986
963
  values["streamId"] = streamId
987
964
  values["keepAlive"] = keepAlive
988
965
  values["keepAliveIntervalSeconds"] = keepAliveIntervalSeconds
989
- values["flash"] = flash
966
+ // The camera light is a privacy indicator and cannot be disabled by SDK callers.
967
+ values["flash"] = true
990
968
  values["sound"] = sound
991
969
  if let videoValues = video?.dictionary, !videoValues.isEmpty {
992
970
  values["video"] = videoValues
@@ -1044,8 +1022,8 @@ public struct RgbLedRequest {
1044
1022
  public let packageName: String?
1045
1023
  public let action: RgbLedAction
1046
1024
  public let color: RgbLedColor?
1047
- public let ontime: Int
1048
- public let offtime: Int
1025
+ public let onDurationMs: Int
1026
+ public let offDurationMs: Int
1049
1027
  public let count: Int
1050
1028
 
1051
1029
  public init(
@@ -1053,16 +1031,16 @@ public struct RgbLedRequest {
1053
1031
  packageName: String?,
1054
1032
  action: RgbLedAction,
1055
1033
  color: RgbLedColor?,
1056
- ontime: Int,
1057
- offtime: Int,
1034
+ onDurationMs: Int,
1035
+ offDurationMs: Int,
1058
1036
  count: Int
1059
1037
  ) {
1060
1038
  self.requestId = requestId
1061
1039
  self.packageName = packageName
1062
1040
  self.action = action
1063
1041
  self.color = color
1064
- self.ontime = ontime
1065
- self.offtime = offtime
1042
+ self.onDurationMs = onDurationMs
1043
+ self.offDurationMs = offDurationMs
1066
1044
  self.count = count
1067
1045
  }
1068
1046
  }
@@ -1070,13 +1048,11 @@ public struct RgbLedRequest {
1070
1048
  public struct VideoRecordingRequest {
1071
1049
  public let requestId: String
1072
1050
  public let save: Bool
1073
- public let flash: Bool
1074
1051
  public let sound: Bool
1075
1052
 
1076
- public init(requestId: String, save: Bool, flash: Bool, sound: Bool) {
1053
+ public init(requestId: String, save: Bool, sound: Bool) {
1077
1054
  self.requestId = requestId
1078
1055
  self.save = save
1079
- self.flash = flash
1080
1056
  self.sound = sound
1081
1057
  }
1082
1058
  }
@@ -1105,11 +1081,11 @@ public struct TouchEvent: CustomStringConvertible {
1105
1081
  }
1106
1082
 
1107
1083
  public var deviceModel: String? {
1108
- stringValue(values, "device_model", "deviceModel")
1084
+ stringValue(values, "deviceModel")
1109
1085
  }
1110
1086
 
1111
1087
  public var gestureName: String? {
1112
- stringValue(values, "gesture_name", "gestureName")
1088
+ stringValue(values, "gestureName")
1113
1089
  }
1114
1090
 
1115
1091
  public var timestamp: Int? {
@@ -1339,7 +1315,7 @@ public enum HotspotStatus: CustomStringConvertible, Equatable {
1339
1315
  enabled: enabled,
1340
1316
  ssid: nonEmptyStringValue(values, "hotspotSsid"),
1341
1317
  password: nonEmptyStringValue(values, "hotspotPassword"),
1342
- localIp: nonEmptyStringValue(values, "hotspotGatewayIp", "hotspotLocalIp")
1318
+ localIp: nonEmptyStringValue(values, "hotspotGatewayIp")
1343
1319
  )
1344
1320
  }
1345
1321
 
@@ -1438,7 +1414,7 @@ public struct HotspotErrorEvent: CustomStringConvertible {
1438
1414
  }
1439
1415
 
1440
1416
  public var message: String? {
1441
- stringValue(values, "error_message", "message", "error")
1417
+ stringValue(values, "errorMessage")
1442
1418
  }
1443
1419
 
1444
1420
  public var timestamp: Int? {
@@ -1456,24 +1432,24 @@ public enum PhotoResponse: CustomStringConvertible, Equatable {
1456
1432
  case error
1457
1433
  }
1458
1434
 
1459
- case success(requestId: String, photoUrl: String, timestamp: Int)
1435
+ case success(requestId: String, uploadUrl: String, timestamp: Int)
1460
1436
  case error(requestId: String, errorCode: String?, errorMessage: String, timestamp: Int)
1461
1437
 
1462
1438
  public init(values: [String: Any]) {
1463
- let requestId = stringValue(values, "requestId", "request_id") ?? ""
1439
+ let requestId = stringValue(values, "requestId") ?? ""
1464
1440
  let timestamp = intValue(values["timestamp"]) ?? Int(Date().timeIntervalSince1970 * 1000)
1465
- let state = stringValue(values, "state", "status")?.lowercased()
1466
- if state == State.success.rawValue || boolValue(values, "success") == true {
1441
+ let state = stringValue(values, "state")?.lowercased()
1442
+ if state == State.success.rawValue {
1467
1443
  self = .success(
1468
1444
  requestId: requestId,
1469
- photoUrl: stringValue(values, "photoUrl", "photo_url") ?? "",
1445
+ uploadUrl: stringValue(values, "uploadUrl") ?? "",
1470
1446
  timestamp: timestamp
1471
1447
  )
1472
1448
  } else {
1473
1449
  self = .error(
1474
1450
  requestId: requestId,
1475
- errorCode: stringValue(values, "errorCode", "error_code"),
1476
- errorMessage: stringValue(values, "errorMessage", "error_message", "error") ?? "Unknown photo error",
1451
+ errorCode: stringValue(values, "errorCode"),
1452
+ errorMessage: stringValue(values, "errorMessage") ?? "Unknown photo error",
1477
1453
  timestamp: timestamp
1478
1454
  )
1479
1455
  }
@@ -1504,11 +1480,11 @@ public enum PhotoResponse: CustomStringConvertible, Equatable {
1504
1480
 
1505
1481
  public var values: [String: Any] {
1506
1482
  switch self {
1507
- case let .success(requestId, photoUrl, timestamp):
1483
+ case let .success(requestId, uploadUrl, timestamp):
1508
1484
  return [
1509
1485
  "state": State.success.rawValue,
1510
1486
  "requestId": requestId,
1511
- "photoUrl": photoUrl,
1487
+ "uploadUrl": uploadUrl,
1512
1488
  "timestamp": timestamp,
1513
1489
  ]
1514
1490
  case let .error(requestId, errorCode, errorMessage, timestamp):
@@ -1607,10 +1583,10 @@ public enum StreamStatus: CustomStringConvertible, Equatable {
1607
1583
 
1608
1584
  public init(values: [String: Any]) {
1609
1585
  let rawState = stringValue(values, "status")
1610
- let streamId = stringValue(values, "streamId", "stream_id")
1586
+ let streamId = stringValue(values, "streamId")
1611
1587
  let timestamp = intValue(values["timestamp"])
1612
1588
  let attempt = optionalIntValue(values, "attempt")
1613
- let maxAttempts = optionalIntValue(values, "maxAttempts", "max_attempts") ?? 0
1589
+ let maxAttempts = optionalIntValue(values, "maxAttempts") ?? 0
1614
1590
 
1615
1591
  if hasAnyKey(values, "streaming") || hasAnyKey(values, "reconnecting") {
1616
1592
  let streaming = boolValue(values, "streaming") == true
@@ -1652,7 +1628,7 @@ public enum StreamStatus: CustomStringConvertible, Equatable {
1652
1628
  case .error:
1653
1629
  self = .error(
1654
1630
  streamId: streamId,
1655
- errorDetails: stringValue(values, "errorDetails", "error_details", "details", "error", "errorMessage")
1631
+ errorDetails: stringValue(values, "errorDetails")
1656
1632
  ?? (rawState == "error_not_streaming" ? "not_streaming" : "Unknown stream error"),
1657
1633
  timestamp: timestamp
1658
1634
  )
@@ -1798,8 +1774,8 @@ public struct KeepAliveAckEvent: CustomStringConvertible, Equatable {
1798
1774
  }
1799
1775
 
1800
1776
  public init(values: [String: Any]) {
1801
- self.streamId = stringValue(values, "streamId", "stream_id") ?? ""
1802
- self.ackId = stringValue(values, "ackId", "ack_id") ?? ""
1777
+ self.streamId = stringValue(values, "streamId") ?? ""
1778
+ self.ackId = stringValue(values, "ackId") ?? ""
1803
1779
  self.timestamp = intValue(values["timestamp"])
1804
1780
  }
1805
1781
 
@@ -1900,6 +1876,26 @@ public enum ScanStopReason {
1900
1876
  case error
1901
1877
  }
1902
1878
 
1879
+ @MainActor
1880
+ public final class ScanSession {
1881
+ private let stopAction: () -> Void
1882
+ private var stopped = false
1883
+
1884
+ init(stopAction: @escaping () -> Void) {
1885
+ self.stopAction = stopAction
1886
+ }
1887
+
1888
+ public func stop() {
1889
+ guard !stopped else { return }
1890
+ stopped = true
1891
+ stopAction()
1892
+ }
1893
+
1894
+ fileprivate func markStopped() {
1895
+ stopped = true
1896
+ }
1897
+ }
1898
+
1903
1899
  public struct LocalTranscriptionEvent: CustomStringConvertible {
1904
1900
  public let text: String
1905
1901
  public let isFinal: Bool
@@ -1916,6 +1912,38 @@ public struct LocalTranscriptionEvent: CustomStringConvertible {
1916
1912
  }
1917
1913
  }
1918
1914
 
1915
+ public struct GlassesMediaVolumeGetResult: CustomStringConvertible {
1916
+ public let level: Int?
1917
+ public let statusCode: Int?
1918
+ public let values: [String: Any]
1919
+
1920
+ public init(values: [String: Any]) {
1921
+ self.level = intValue(values["level"])
1922
+ self.statusCode = intValue(values["statusCode"])
1923
+ self.values = values
1924
+ }
1925
+
1926
+ public var description: String {
1927
+ let levelText = level.map(String.init) ?? "unknown"
1928
+ let statusCodeText = statusCode.map(String.init) ?? "unknown"
1929
+ return "GlassesMediaVolumeGetResult(level: \(levelText), statusCode: \(statusCodeText))"
1930
+ }
1931
+ }
1932
+
1933
+ public struct GlassesMediaVolumeSetResult: CustomStringConvertible {
1934
+ public let statusCode: Int?
1935
+ public let values: [String: Any]
1936
+
1937
+ public init(values: [String: Any]) {
1938
+ self.statusCode = intValue(values["statusCode"])
1939
+ self.values = values
1940
+ }
1941
+
1942
+ public var description: String {
1943
+ "GlassesMediaVolumeSetResult(statusCode: \(statusCode.map(String.init) ?? "unknown"))"
1944
+ }
1945
+ }
1946
+
1919
1947
  public enum BluetoothEvent: CustomStringConvertible {
1920
1948
  case buttonPress(ButtonPressEvent)
1921
1949
  case touch(TouchEvent)
@@ -1982,6 +2010,26 @@ public extension MentraBluetoothSDKDelegate {
1982
2010
  func mentraBluetoothSDK(_: MentraBluetoothSDK, didFail _: BluetoothError) {}
1983
2011
  }
1984
2012
 
2013
+ @MainActor
2014
+ private final class ActiveScanSession {
2015
+ let model: DeviceModel
2016
+ let onResults: ([Device]) -> Void
2017
+ let onComplete: ([Device]) -> Void
2018
+ var latestResults: [Device] = []
2019
+ var timeoutTask: Task<Void, Never>?
2020
+ weak var publicSession: ScanSession?
2021
+
2022
+ init(
2023
+ model: DeviceModel,
2024
+ onResults: @escaping ([Device]) -> Void,
2025
+ onComplete: @escaping ([Device]) -> Void
2026
+ ) {
2027
+ self.model = model
2028
+ self.onResults = onResults
2029
+ self.onComplete = onComplete
2030
+ }
2031
+ }
2032
+
1985
2033
  @MainActor
1986
2034
  public final class MentraBluetoothSDK {
1987
2035
  public weak var delegate: MentraBluetoothSDKDelegate?
@@ -1993,6 +2041,7 @@ public final class MentraBluetoothSDK {
1993
2041
  private let defaultDeviceKeys: Set<String> = ["default_wearable", "device_name", "device_address"]
1994
2042
  private var suppressDefaultDeviceEvents = false
1995
2043
  private var defaultDeviceApplyGeneration = 0
2044
+ private var activeScanSessions: [UUID: ActiveScanSession] = [:]
1996
2045
 
1997
2046
  public init(configuration: MentraBluetoothSDKConfiguration = .default) {
1998
2047
  self.configuration = configuration
@@ -2059,9 +2108,50 @@ public final class MentraBluetoothSDK {
2059
2108
  }
2060
2109
 
2061
2110
  public func stopScan() {
2111
+ stopScan(reason: .cancelled)
2112
+ }
2113
+
2114
+ private func stopScan(reason: ScanStopReason) {
2062
2115
  DeviceManager.shared.stopScan()
2063
2116
  DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "searching", false)
2064
- delegate?.mentraBluetoothSDK(self, didStopScan: .cancelled)
2117
+ delegate?.mentraBluetoothSDK(self, didStopScan: reason)
2118
+ }
2119
+
2120
+ @discardableResult
2121
+ public func scan(
2122
+ model: DeviceModel,
2123
+ timeout: TimeInterval = 15,
2124
+ onResults: @escaping ([Device]) -> Void,
2125
+ onComplete: @escaping ([Device]) -> Void = { _ in }
2126
+ ) throws -> ScanSession {
2127
+ let normalizedTimeout = timeout > 0 && timeout.isFinite ? timeout : 15
2128
+ let id = UUID()
2129
+ let activeSession = ActiveScanSession(
2130
+ model: model,
2131
+ onResults: onResults,
2132
+ onComplete: onComplete
2133
+ )
2134
+ let publicSession = ScanSession { [weak self] in
2135
+ self?.finishScanSession(id, reason: .cancelled, shouldStopScan: true)
2136
+ }
2137
+ activeSession.publicSession = publicSession
2138
+ activeScanSessions[id] = activeSession
2139
+
2140
+ do {
2141
+ emitScanResults([], forSession: id)
2142
+ try startScan(model: model)
2143
+ emitScanResults(bluetoothStatus.searchResults.filter { $0.model == model }, forSession: id)
2144
+ activeSession.timeoutTask = Task { [weak self] in
2145
+ let nanoseconds = UInt64(normalizedTimeout * 1_000_000_000)
2146
+ try? await Task.sleep(nanoseconds: nanoseconds)
2147
+ await self?.finishScanSession(id, reason: .completed, shouldStopScan: true)
2148
+ }
2149
+ return publicSession
2150
+ } catch {
2151
+ activeScanSessions[id] = nil
2152
+ publicSession.markStopped()
2153
+ throw error
2154
+ }
2065
2155
  }
2066
2156
 
2067
2157
  public func connect(to device: Device, options: ConnectOptions = ConnectOptions()) throws {
@@ -2103,7 +2193,7 @@ public final class MentraBluetoothSDK {
2103
2193
  DeviceManager.shared.disconnect()
2104
2194
  }
2105
2195
 
2106
- public func connectSimulated() {
2196
+ func connectSimulated() {
2107
2197
  DeviceManager.shared.connectSimulated()
2108
2198
  }
2109
2199
 
@@ -2123,7 +2213,7 @@ public final class MentraBluetoothSDK {
2123
2213
  DeviceManager.shared.displayText(request.dictionary)
2124
2214
  }
2125
2215
 
2126
- public func displayEvent(_ request: DisplayEventRequest) async throws {
2216
+ func displayEvent(_ request: DisplayEventRequest) async throws {
2127
2217
  DeviceManager.shared.displayEvent(request.values)
2128
2218
  }
2129
2219
 
@@ -2135,14 +2225,14 @@ public final class MentraBluetoothSDK {
2135
2225
  DeviceManager.shared.showDashboard()
2136
2226
  }
2137
2227
 
2138
- public func setBrightness(_ level: Int, autoMode: Bool? = nil) async throws {
2228
+ func setBrightness(_ level: Int, autoMode: Bool? = nil) async throws {
2139
2229
  if let autoMode {
2140
2230
  DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "auto_brightness", autoMode)
2141
2231
  }
2142
2232
  DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "brightness", level)
2143
2233
  }
2144
2234
 
2145
- public func setAutoBrightness(enabled: Bool) async throws {
2235
+ func setAutoBrightness(enabled: Bool) async throws {
2146
2236
  DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "auto_brightness", enabled)
2147
2237
  }
2148
2238
 
@@ -2155,7 +2245,7 @@ public final class MentraBluetoothSDK {
2155
2245
  try await setDashboardPosition(height: request.height, depth: request.depth)
2156
2246
  }
2157
2247
 
2158
- public func setDashboardMenu(_ items: [DashboardMenuItem]) async throws {
2248
+ func setDashboardMenu(_ items: [DashboardMenuItem]) async throws {
2159
2249
  DeviceStore.shared.apply(
2160
2250
  ObservableStore.bluetoothCategory,
2161
2251
  "menu_apps",
@@ -2172,7 +2262,7 @@ public final class MentraBluetoothSDK {
2172
2262
  }
2173
2263
 
2174
2264
  public func setGalleryMode(_ mode: GalleryMode) async throws {
2175
- DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "gallery_mode", mode == .auto)
2265
+ DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "galleryModeAuto", mode == .auto)
2176
2266
  }
2177
2267
 
2178
2268
  public func setButtonPhotoSettings(size: ButtonPhotoSize) async throws {
@@ -2183,14 +2273,14 @@ public final class MentraBluetoothSDK {
2183
2273
  try await setButtonPhotoSettings(size: settings.size)
2184
2274
  }
2185
2275
 
2186
- public func setButtonVideoRecordingSettings(width: Int, height: Int, fps: Int) async throws {
2276
+ public func setButtonVideoRecordingSettings(width: Int, height: Int, frameRate: Int) async throws {
2187
2277
  DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "button_video_width", width)
2188
2278
  DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "button_video_height", height)
2189
- DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "button_video_fps", fps)
2279
+ DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "button_video_fps", frameRate)
2190
2280
  }
2191
2281
 
2192
2282
  public func setButtonVideoRecordingSettings(_ settings: ButtonVideoRecordingSettings) async throws {
2193
- try await setButtonVideoRecordingSettings(width: settings.width, height: settings.height, fps: settings.fps)
2283
+ try await setButtonVideoRecordingSettings(width: settings.width, height: settings.height, frameRate: settings.frameRate)
2194
2284
  }
2195
2285
 
2196
2286
  public func setButtonCameraLed(enabled: Bool) async throws {
@@ -2227,16 +2317,6 @@ public final class MentraBluetoothSDK {
2227
2317
  )
2228
2318
  }
2229
2319
 
2230
- @available(*, deprecated, message: "Use setMicState(enabled:useGlassesMic:bypassVad:) instead.")
2231
- public func setMicState(_ config: MicConfiguration) {
2232
- applyMicState(
2233
- sendPcmData: config.sendPcmData,
2234
- sendTranscript: config.sendTranscript,
2235
- bypassVad: config.bypassVad,
2236
- sendLc3Data: config.sendLc3Data
2237
- )
2238
- }
2239
-
2240
2320
  private func applyMicState(
2241
2321
  sendPcmData: Bool,
2242
2322
  sendTranscript: Bool,
@@ -2258,6 +2338,20 @@ public final class MentraBluetoothSDK {
2258
2338
  PhoneAudioMonitor.getInstance().setOwnAppAudioPlaying(playing)
2259
2339
  }
2260
2340
 
2341
+ public func getGlassesMediaVolume() async throws -> GlassesMediaVolumeGetResult {
2342
+ GlassesMediaVolumeGetResult(values: try await DeviceManager.shared.getGlassesMediaVolume())
2343
+ }
2344
+
2345
+ public func setGlassesMediaVolume(_ level: Int) async throws -> GlassesMediaVolumeSetResult {
2346
+ guard (0...15).contains(level) else {
2347
+ throw BluetoothError(
2348
+ code: "invalid_volume_level",
2349
+ message: "Glasses media volume must be between 0 and 15."
2350
+ )
2351
+ }
2352
+ return GlassesMediaVolumeSetResult(values: try await DeviceManager.shared.setGlassesMediaVolume(level: level))
2353
+ }
2354
+
2261
2355
  public func requestWifiScan() {
2262
2356
  DeviceManager.shared.requestWifiScan()
2263
2357
  }
@@ -2275,14 +2369,13 @@ public final class MentraBluetoothSDK {
2275
2369
  }
2276
2370
 
2277
2371
  public func requestPhoto(_ request: PhotoRequest) {
2278
- DeviceManager.shared.photoRequest(
2372
+ DeviceManager.shared.requestPhoto(
2279
2373
  request.requestId,
2280
2374
  request.appId,
2281
2375
  request.size.rawValue,
2282
2376
  request.webhookUrl,
2283
2377
  request.authToken,
2284
2378
  request.compress?.rawValue,
2285
- request.flash,
2286
2379
  request.sound
2287
2380
  )
2288
2381
  }
@@ -2305,8 +2398,8 @@ public final class MentraBluetoothSDK {
2305
2398
  packageName: request.packageName,
2306
2399
  action: request.action.rawValue,
2307
2400
  color: request.color?.rawValue,
2308
- ontime: request.ontime,
2309
- offtime: request.offtime,
2401
+ onDurationMs: request.onDurationMs,
2402
+ offDurationMs: request.offDurationMs,
2310
2403
  count: request.count
2311
2404
  )
2312
2405
  }
@@ -2319,7 +2412,6 @@ public final class MentraBluetoothSDK {
2319
2412
  DeviceManager.shared.startVideoRecording(
2320
2413
  request.requestId,
2321
2414
  request.save,
2322
- request.flash,
2323
2415
  request.sound
2324
2416
  )
2325
2417
  }
@@ -2332,23 +2424,23 @@ public final class MentraBluetoothSDK {
2332
2424
  DeviceManager.shared.requestVersionInfo()
2333
2425
  }
2334
2426
 
2335
- public func sendOtaStart() {
2427
+ func sendOtaStart() {
2336
2428
  DeviceManager.shared.sendOtaStart()
2337
2429
  }
2338
2430
 
2339
- public func sendOtaQueryStatus() {
2431
+ func sendOtaQueryStatus() {
2340
2432
  DeviceManager.shared.sendOtaQueryStatus()
2341
2433
  }
2342
2434
 
2343
- public func sendShutdown() {
2435
+ func sendShutdown() {
2344
2436
  DeviceManager.shared.sendShutdown()
2345
2437
  }
2346
2438
 
2347
- public func sendReboot() {
2439
+ func sendReboot() {
2348
2440
  DeviceManager.shared.sendReboot()
2349
2441
  }
2350
2442
 
2351
- public func sendIncidentId(_ incidentId: String, apiBaseUrl: String? = nil) {
2443
+ func sendIncidentId(_ incidentId: String, apiBaseUrl: String? = nil) {
2352
2444
  DeviceManager.shared.sendIncidentId(incidentId, apiBaseUrl: apiBaseUrl)
2353
2445
  }
2354
2446
 
@@ -2374,6 +2466,7 @@ public final class MentraBluetoothSDK {
2374
2466
  dispatchDefaultDeviceChanged()
2375
2467
  }
2376
2468
  dispatchDiscoveredDevices(changes["searchResults"])
2469
+ dispatchScanResults(changes["searchResults"])
2377
2470
  default:
2378
2471
  break
2379
2472
  }
@@ -2428,13 +2521,38 @@ public final class MentraBluetoothSDK {
2428
2521
  private func dispatchDiscoveredDevices(_ rawSearchResults: Any?) {
2429
2522
  guard let results = rawSearchResults as? [[String: Any]] else { return }
2430
2523
  for result in results {
2431
- guard let name = result["deviceName"] as? String ?? result["name"] as? String else { continue }
2524
+ guard let name = result["name"] as? String else { continue }
2432
2525
  guard discoveredDeviceNames.insert(name).inserted else { continue }
2433
2526
  guard let device = Device(values: result) else { continue }
2434
2527
  delegate?.mentraBluetoothSDK(self, didDiscover: device)
2435
2528
  }
2436
2529
  }
2437
2530
 
2531
+ private func dispatchScanResults(_ rawSearchResults: Any?) {
2532
+ guard let results = rawSearchResults as? [[String: Any]] else { return }
2533
+ let devices = results.compactMap(Device.init(values:))
2534
+ for id in Array(activeScanSessions.keys) {
2535
+ guard let activeSession = activeScanSessions[id] else { continue }
2536
+ emitScanResults(devices.filter { $0.model == activeSession.model }, forSession: id)
2537
+ }
2538
+ }
2539
+
2540
+ private func emitScanResults(_ devices: [Device], forSession id: UUID) {
2541
+ guard let activeSession = activeScanSessions[id] else { return }
2542
+ activeSession.latestResults = devices
2543
+ activeSession.onResults(devices)
2544
+ }
2545
+
2546
+ private func finishScanSession(_ id: UUID, reason: ScanStopReason, shouldStopScan: Bool) {
2547
+ guard let activeSession = activeScanSessions.removeValue(forKey: id) else { return }
2548
+ activeSession.timeoutTask?.cancel()
2549
+ activeSession.publicSession?.markStopped()
2550
+ if shouldStopScan {
2551
+ stopScan(reason: reason)
2552
+ }
2553
+ activeSession.onComplete(activeSession.latestResults)
2554
+ }
2555
+
2438
2556
  private func dispatchBridgeEvent(_ eventName: String, _ data: [String: Any]) {
2439
2557
  switch eventName {
2440
2558
  case "log":