@mentra/bluetooth-sdk 0.1.8 → 0.1.10

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 (147) hide show
  1. package/README.md +116 -18
  2. package/android/build.gradle +289 -16
  3. package/android/gradle.properties.example +9 -0
  4. package/android/lc3Lib/build.gradle +95 -6
  5. package/android/settings.gradle +3 -0
  6. package/android/silero/LICENSE.md +17 -0
  7. package/android/silero/build.gradle +42 -0
  8. package/android/silero/proguard-rules.pro +24 -0
  9. package/android/silero/src/androidTest/assets/hello.wav +0 -0
  10. package/android/silero/src/androidTest/java/com/konovalov/vad/silero/VadSileroTest.kt +79 -0
  11. package/android/silero/src/main/assets/LICENSE +21 -0
  12. package/android/silero/src/main/assets/silero_vad.onnx +0 -0
  13. package/android/silero/src/main/java/com/konovalov/vad/silero/Vad.kt +160 -0
  14. package/android/silero/src/main/java/com/konovalov/vad/silero/VadSilero.kt +449 -0
  15. package/android/silero/src/main/java/com/konovalov/vad/silero/config/FrameSize.kt +15 -0
  16. package/android/silero/src/main/java/com/konovalov/vad/silero/config/Mode.kt +14 -0
  17. package/android/silero/src/main/java/com/konovalov/vad/silero/config/SampleRate.kt +12 -0
  18. package/android/silero/src/main/java/com/konovalov/vad/silero/utils/AudioUtils.kt +47 -0
  19. package/android/silero/src/main/java/com/konovalov/vad/silero/utils/TensorMap.kt +41 -0
  20. package/android/silero/src/test/java/com/konovalov/vad/silero/utils/AudioUtilsTest.kt +44 -0
  21. package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkDefaults.kt +6 -0
  22. package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +165 -27
  23. package/android/src/main/java/com/mentra/bluetoothsdk/Bridge.kt +89 -21
  24. package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +189 -16
  25. package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +22 -2
  26. package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +787 -85
  27. package/android/src/main/java/com/mentra/bluetoothsdk/audio/AudioModels.kt +6 -2
  28. package/android/src/main/java/com/mentra/bluetoothsdk/camera/CameraModels.kt +123 -10
  29. package/android/src/main/java/com/mentra/bluetoothsdk/controllers/ControllerManager.kt +12 -2
  30. package/android/src/main/java/com/mentra/bluetoothsdk/controllers/R1.kt +8 -2
  31. package/android/src/main/java/com/mentra/bluetoothsdk/debug/BleTraceLogger.kt +33 -17
  32. package/android/src/main/java/com/mentra/bluetoothsdk/events/BluetoothEvents.kt +101 -0
  33. package/android/src/main/java/com/mentra/bluetoothsdk/internal/MapParsing.kt +8 -0
  34. package/android/src/main/java/com/mentra/bluetoothsdk/photoreceiver/LocalPhotoReceiverRegistry.java +108 -0
  35. package/android/src/main/java/com/mentra/bluetoothsdk/photoreceiver/LocalPhotoUploadServer.kt +399 -74
  36. package/android/src/main/java/com/mentra/bluetoothsdk/photoreceiver/MentraPhotoReceiverModule.kt +100 -17
  37. package/android/src/main/java/com/mentra/bluetoothsdk/requests/DisplayRequests.kt +17 -0
  38. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G1.java +2 -2
  39. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G2.kt +569 -220
  40. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Mach1.java +2 -2
  41. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.java +271 -66
  42. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraNex.kt +176 -104
  43. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraosBle.java +12753 -27036
  44. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/SGCManager.kt +54 -2
  45. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Simulated.kt +16 -4
  46. package/android/src/main/java/com/mentra/bluetoothsdk/status/DeviceStatus.kt +60 -3
  47. package/android/src/main/java/com/mentra/bluetoothsdk/streaming/StreamModels.kt +12 -3
  48. package/android/src/main/java/com/mentra/bluetoothsdk/stt/STTTools.kt +87 -36
  49. package/android/src/main/java/com/mentra/bluetoothsdk/stt/SherpaOnnxTranscriber.kt +13 -4
  50. package/android/src/main/java/com/mentra/bluetoothsdk/stt/VadGateSpeechPolicy.kt +229 -0
  51. package/android/src/main/java/com/mentra/bluetoothsdk/tts/TTSTools.kt +180 -0
  52. package/android/src/main/java/com/mentra/bluetoothsdk/utils/AvifExifStripper.java +573 -0
  53. package/android/src/main/java/com/mentra/bluetoothsdk/utils/BlePhotoUploadService.java +488 -50
  54. package/android/src/main/java/com/mentra/bluetoothsdk/utils/HeifExifTagReader.java +199 -0
  55. package/android/src/main/java/com/mentra/bluetoothsdk/utils/MessageChunkReassembler.java +18 -1
  56. package/android/src/main/java/com/mentra/bluetoothsdk/utils/MessageChunker.java +64 -18
  57. package/android/src/main/java/com/mentra/bluetoothsdk/utils/NexSGCUtils.kt +48 -39
  58. package/android/src/test/java/com/mentra/bluetoothsdk/camera/PhotoRequestTest.kt +23 -0
  59. package/android/src/test/java/com/mentra/bluetoothsdk/photoreceiver/LocalPhotoReceiverRegistryTest.java +75 -0
  60. package/android/src/test/java/com/mentra/bluetoothsdk/utils/AvifExifStripperTest.java +30 -0
  61. package/android/src/test/java/com/mentra/bluetoothsdk/utils/BlePhotoUploadServiceTest.java +98 -0
  62. package/android/src/test/resources/avif_with_exif.avif +0 -0
  63. package/build/BluetoothSdk.types.d.ts +241 -43
  64. package/build/BluetoothSdk.types.d.ts.map +1 -1
  65. package/build/BluetoothSdk.types.js +11 -10
  66. package/build/BluetoothSdk.types.js.map +1 -1
  67. package/build/_private/BluetoothSdkModule.d.ts +36 -23
  68. package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
  69. package/build/_private/BluetoothSdkModule.js +30 -52
  70. package/build/_private/BluetoothSdkModule.js.map +1 -1
  71. package/build/_private/photoRequestPayload.d.ts +4 -0
  72. package/build/_private/photoRequestPayload.d.ts.map +1 -0
  73. package/build/_private/photoRequestPayload.js +25 -0
  74. package/build/_private/photoRequestPayload.js.map +1 -0
  75. package/build/index.d.ts +1 -1
  76. package/build/index.d.ts.map +1 -1
  77. package/build/index.js +23 -0
  78. package/build/index.js.map +1 -1
  79. package/build/react/useMentraBluetooth.d.ts +2 -2
  80. package/build/react/useMentraBluetooth.d.ts.map +1 -1
  81. package/build/react/useMentraBluetooth.js +3 -3
  82. package/build/react/useMentraBluetooth.js.map +1 -1
  83. package/ios/BluetoothSdkModule.swift +174 -70
  84. package/ios/LocalPhotoUploadServer.swift +370 -105
  85. package/ios/MentraBluetoothSDK.podspec +3 -1
  86. package/ios/MentraPhotoReceiverModule.swift +62 -10
  87. package/ios/Packages/CoreObjC/include/PcmConverter.h +22 -0
  88. package/ios/Packages/SherpaOnnx/SherpaOnnx.swift +668 -26
  89. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/Info.plist +13 -9
  90. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64/Headers/sherpa-onnx/c-api/c-api.h +4551 -0
  91. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64/Headers/sherpa-onnx/c-api/cxx-api.h +1745 -0
  92. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64/libsherpa-onnx.a +0 -0
  93. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64_x86_64-simulator/Headers/sherpa-onnx/c-api/c-api.h +4551 -0
  94. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64_x86_64-simulator/Headers/sherpa-onnx/c-api/cxx-api.h +1745 -0
  95. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64_x86_64-simulator/libsherpa-onnx.a +0 -0
  96. package/ios/Source/BluetoothSdkDefaults.swift +6 -0
  97. package/ios/Source/Bridge.swift +58 -19
  98. package/ios/Source/DeviceManager.swift +149 -17
  99. package/ios/Source/DeviceStore.swift +16 -6
  100. package/ios/Source/MentraBluetoothSDK.swift +803 -47
  101. package/ios/Source/ObservableStore.swift +8 -2
  102. package/ios/Source/{Audio → audio}/AudioModels.swift +9 -7
  103. package/ios/Source/{Camera → camera}/CameraModels.swift +225 -16
  104. package/ios/Source/controllers/ControllerManager.swift +4 -3
  105. package/ios/Source/controllers/R1.swift +3 -3
  106. package/ios/Source/{Events → events}/BluetoothEvents.swift +183 -1
  107. package/ios/Source/{Internal → internal}/ValueParsing.swift +10 -1
  108. package/ios/Source/{Requests → requests}/DisplayRequests.swift +28 -4
  109. package/ios/Source/services/PhoneMic.swift +1 -0
  110. package/ios/Source/sgcs/G1.swift +2 -2
  111. package/ios/Source/sgcs/G2.swift +645 -403
  112. package/ios/Source/sgcs/Mach1.swift +2 -2
  113. package/ios/Source/sgcs/MentraLive.swift +812 -319
  114. package/ios/Source/sgcs/MentraNex.swift +412 -182
  115. package/ios/Source/sgcs/SGCManager.swift +57 -3
  116. package/ios/Source/sgcs/Simulated.swift +7 -3
  117. package/ios/Source/sgcs/mentraos_ble.pb.swift +3073 -3134
  118. package/ios/Source/status/DeviceStatus.swift +974 -0
  119. package/ios/Source/{Status → status}/WifiHotspotStatus.swift +4 -4
  120. package/ios/Source/{Streaming → streaming}/StreamModels.swift +24 -9
  121. package/ios/Source/stt/STTTools.swift +27 -12
  122. package/ios/Source/stt/SherpaOnnxTranscriber.swift +31 -8
  123. package/ios/Source/tts/TTSTools.swift +164 -0
  124. package/ios/Source/utils/JSCExperiment.swift +7 -7
  125. package/ios/Source/utils/MessageChunkReassembler.swift +20 -1
  126. package/ios/Source/utils/MessageChunker.swift +78 -21
  127. package/ios/Source/utils/TarBz2Extractor.swift +53 -2
  128. package/package.json +1 -1
  129. package/plugin/build/withAndroid.js +68 -4
  130. package/src/BluetoothSdk.types.ts +331 -59
  131. package/src/_private/BluetoothSdkModule.ts +113 -102
  132. package/src/_private/photoRequestPayload.ts +28 -0
  133. package/src/index.ts +58 -15
  134. package/src/react/useMentraBluetooth.ts +6 -4
  135. package/android/.project +0 -28
  136. package/android/lc3Lib/.project +0 -28
  137. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/Headers/cargs.h +0 -162
  138. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/Headers/sherpa-onnx/c-api/c-api.h +0 -1852
  139. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/Headers/sherpa-onnx/c-api/cxx-api.h +0 -674
  140. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64/sherpa-onnx.a +0 -0
  141. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64_x86_64-simulator/sherpa-onnx.a +0 -0
  142. package/ios/Source/Status/DeviceStatus.swift +0 -471
  143. /package/ios/Source/{Connection → connection}/ScanSession.swift +0 -0
  144. /package/ios/Source/{Errors → errors}/BluetoothError.swift +0 -0
  145. /package/ios/Source/{Internal → internal}/BluetoothAvailability.swift +0 -0
  146. /package/ios/Source/{Status → status}/RuntimeState.swift +0 -0
  147. /package/ios/Source/{Types → types}/DeviceModels.swift +0 -0
@@ -9,7 +9,7 @@ import Foundation
9
9
 
10
10
  @MainActor
11
11
  class ObservableStore {
12
- private nonisolated(unsafe) var values: [String: Any] = [:]
12
+ private var values: [String: Any] = [:]
13
13
  private var onEmit: ((String, [String: Any]) -> Void)?
14
14
  private var listeners: [String: (String, [String: Any]) -> Void] = [:]
15
15
 
@@ -54,10 +54,16 @@ class ObservableStore {
54
54
  }
55
55
  }
56
56
 
57
- nonisolated func get(_ category: String, _ key: String) -> Any? {
57
+ func get(_ category: String, _ key: String) -> Any? {
58
58
  values["\(Self.normalizeCategory(category)).\(key)"]
59
59
  }
60
60
 
61
+ func wouldSkipSet(_ category: String, _ key: String, _ value: Any) -> Bool {
62
+ let fullKey = "\(Self.normalizeCategory(category)).\(key)"
63
+ guard let oldValue = values[fullKey] else { return false }
64
+ return areEqual(oldValue, value)
65
+ }
66
+
61
67
  func getCategory(_ category: String) -> [String: Any] {
62
68
  var result: [String: Any] = [:]
63
69
  let prefix = "\(Self.normalizeCategory(category))."
@@ -24,7 +24,7 @@ public struct LocalTranscriptionEvent: CustomStringConvertible {
24
24
  }
25
25
 
26
26
  public struct MicPcmEvent: CustomStringConvertible {
27
- public static let sampleRate = 16_000
27
+ public static let sampleRate = 16000
28
28
  public static let bitsPerSample = 16
29
29
  public static let channels = 1
30
30
  public static let encoding = "pcm_s16le"
@@ -43,7 +43,8 @@ public struct MicPcmEvent: CustomStringConvertible {
43
43
  let bitsPerSample = intValue(values["bitsPerSample"]) ?? Self.bitsPerSample
44
44
  let channels = intValue(values["channels"]) ?? Self.channels
45
45
  let encoding = values["encoding"] as? String ?? Self.encoding
46
- let voiceActivityDetectionEnabled = boolValue(values, "voiceActivityDetectionEnabled") ?? true
46
+ let voiceActivityDetectionEnabled =
47
+ boolValue(values, "voiceActivityDetectionEnabled") ?? BluetoothSdkDefaults.voiceActivityDetectionEnabled
47
48
 
48
49
  var normalized = values
49
50
  normalized["type"] = "mic_pcm"
@@ -69,7 +70,7 @@ public struct MicPcmEvent: CustomStringConvertible {
69
70
  }
70
71
 
71
72
  public struct MicLc3Event: CustomStringConvertible {
72
- public static let sampleRate = 16_000
73
+ public static let sampleRate = 16000
73
74
  public static let channels = 1
74
75
  public static let encoding = "lc3"
75
76
  public static let frameDurationMs = 10
@@ -95,7 +96,8 @@ public struct MicLc3Event: CustomStringConvertible {
95
96
  let frameSizeBytes = intValue(values["frameSizeBytes"]) ?? Self.defaultFrameSizeBytes
96
97
  let bitrate = intValue(values["bitrate"]) ?? frameSizeBytes * 8 * (1000 / frameDurationMs)
97
98
  let packetizedFromGlasses = boolValue(values, "packetizedFromGlasses") ?? false
98
- let voiceActivityDetectionEnabled = boolValue(values, "voiceActivityDetectionEnabled") ?? true
99
+ let voiceActivityDetectionEnabled =
100
+ boolValue(values, "voiceActivityDetectionEnabled") ?? BluetoothSdkDefaults.voiceActivityDetectionEnabled
99
101
 
100
102
  var normalized = values
101
103
  normalized["type"] = "mic_lc3"
@@ -132,8 +134,8 @@ public struct GlassesMediaVolumeGetResult: CustomStringConvertible {
132
134
  public let values: [String: Any]
133
135
 
134
136
  public init(values: [String: Any]) {
135
- self.level = intValue(values["level"])
136
- self.statusCode = intValue(values["statusCode"])
137
+ level = intValue(values["level"])
138
+ statusCode = intValue(values["statusCode"])
137
139
  self.values = values
138
140
  }
139
141
 
@@ -149,7 +151,7 @@ public struct GlassesMediaVolumeSetResult: CustomStringConvertible {
149
151
  public let values: [String: Any]
150
152
 
151
153
  public init(values: [String: Any]) {
152
- self.statusCode = intValue(values["statusCode"])
154
+ statusCode = intValue(values["statusCode"])
153
155
  self.values = values
154
156
  }
155
157
 
@@ -39,24 +39,101 @@ public struct ButtonVideoRecordingSettings {
39
39
  }
40
40
  }
41
41
 
42
+ public enum CameraRoiPosition: Int {
43
+ case center = 0
44
+ case bottom = 1
45
+ case top = 2
46
+
47
+ public var label: String {
48
+ switch self {
49
+ case .center:
50
+ return "center"
51
+ case .bottom:
52
+ return "bottom"
53
+ case .top:
54
+ return "top"
55
+ }
56
+ }
57
+
58
+ public static func from(rawValue: Int?) -> CameraRoiPosition {
59
+ guard let rawValue else {
60
+ return .center
61
+ }
62
+ return CameraRoiPosition(rawValue: rawValue) ?? .center
63
+ }
64
+ }
65
+
42
66
  public struct CameraFov {
43
- public static let minFov = 82
67
+ public static let minFov = 62
44
68
  public static let maxFov = 118
45
69
  public static let defaultFov = 102
46
- public static let minRoiPosition = 0
47
- public static let maxRoiPosition = 2
48
- public static let defaultRoiPosition = 0
70
+ public static let narrowFov = 82
71
+ public static let defaultRoiPosition = CameraRoiPosition.center
72
+ public static let narrow = CameraFov(
73
+ fov: CameraFov.narrowFov,
74
+ roiPosition: CameraFov.defaultRoiPosition
75
+ )
76
+ public static let standard = CameraFov(
77
+ fov: CameraFov.defaultFov,
78
+ roiPosition: CameraFov.defaultRoiPosition
79
+ )
80
+ public static let wide = CameraFov(
81
+ fov: CameraFov.maxFov,
82
+ roiPosition: CameraFov.defaultRoiPosition
83
+ )
49
84
 
50
85
  public let fov: Int
51
- public let roiPosition: Int
86
+ public let roiPosition: CameraRoiPosition
52
87
 
53
- public init(fov: Int = CameraFov.defaultFov, roiPosition: Int = CameraFov.defaultRoiPosition) {
88
+ public init(fov: Int = CameraFov.defaultFov, roiPosition: CameraRoiPosition = CameraFov.defaultRoiPosition) {
54
89
  self.fov = min(max(fov, CameraFov.minFov), CameraFov.maxFov)
55
- self.roiPosition = min(max(roiPosition, CameraFov.minRoiPosition), CameraFov.maxRoiPosition)
90
+ self.roiPosition = roiPosition
56
91
  }
57
92
 
58
93
  var value: [String: Int] {
59
- ["fov": fov, "roi_position": roiPosition]
94
+ ["fov": fov, "roi_position": roiPosition.rawValue]
95
+ }
96
+ }
97
+
98
+ public struct CameraFovResult: CustomStringConvertible {
99
+ public let requestId: String
100
+ public let fov: Int
101
+ public let roiPosition: CameraRoiPosition
102
+ public let timestamp: Int
103
+
104
+ public var values: [String: Any] {
105
+ [
106
+ "requestId": requestId,
107
+ "fov": fov,
108
+ "roiPosition": roiPosition.label,
109
+ "timestamp": timestamp,
110
+ ]
111
+ }
112
+
113
+ public var description: String {
114
+ "CameraFovResult(fov: \(fov), roiPosition: \(roiPosition.label))"
115
+ }
116
+
117
+ static func from(ack: SettingsAckEvent, fallback: CameraFov) throws -> CameraFovResult {
118
+ if ack.status == "error" {
119
+ throw BluetoothError(
120
+ code: ack.errorCode ?? "camera_fov_failed",
121
+ message: ack.errorMessage ?? "Camera FOV request failed."
122
+ )
123
+ }
124
+ if !ack.hardwareApplied {
125
+ throw BluetoothError(
126
+ code: "camera_fov_not_applied",
127
+ message: "Camera FOV was saved but not applied to hardware."
128
+ )
129
+ }
130
+
131
+ return CameraFovResult(
132
+ requestId: ack.requestId,
133
+ fov: ack.fov ?? fallback.fov,
134
+ roiPosition: CameraRoiPosition.from(rawValue: ack.roiPosition ?? fallback.roiPosition.rawValue),
135
+ timestamp: ack.timestamp
136
+ )
60
137
  }
61
138
  }
62
139
 
@@ -68,6 +145,7 @@ public struct PhotoRequest {
68
145
  public let authToken: String?
69
146
  public let compress: PhotoCompression?
70
147
  public let flash: Bool
148
+ public let save: Bool
71
149
  public let sound: Bool
72
150
  /// Sensor exposure time for this capture only (ns), or nil for auto exposure
73
151
  public let exposureTimeNs: Double?
@@ -82,6 +160,7 @@ public struct PhotoRequest {
82
160
  authToken: String? = nil,
83
161
  compress: PhotoCompression? = nil,
84
162
  flash: Bool = true,
163
+ save: Bool = false,
85
164
  sound: Bool,
86
165
  exposureTimeNs: Double? = nil,
87
166
  iso: Int? = nil
@@ -93,6 +172,7 @@ public struct PhotoRequest {
93
172
  self.authToken = authToken
94
173
  self.compress = compress
95
174
  self.flash = flash
175
+ self.save = save
96
176
  self.sound = sound
97
177
  self.exposureTimeNs = exposureTimeNs
98
178
  self.iso = iso
@@ -144,11 +224,58 @@ public struct VideoRecordingRequest {
144
224
  public let requestId: String
145
225
  public let save: Bool
146
226
  public let sound: Bool
227
+ // Optional per-recording overrides; 0 means "use the saved button-video default".
228
+ public let width: Int
229
+ public let height: Int
230
+ public let fps: Int
147
231
 
148
- public init(requestId: String, save: Bool, sound: Bool) {
232
+ public init(
233
+ requestId: String, save: Bool, sound: Bool, width: Int = 0, height: Int = 0, fps: Int = 0
234
+ ) {
149
235
  self.requestId = requestId
150
236
  self.save = save
151
237
  self.sound = sound
238
+ self.width = width
239
+ self.height = height
240
+ self.fps = fps
241
+ }
242
+ }
243
+
244
+ public struct VideoRecordingStatusEvent: CustomStringConvertible {
245
+ public let values: [String: Any]
246
+
247
+ public init(values: [String: Any]) {
248
+ var values = values
249
+ values["type"] = "video_recording_status"
250
+ self.values = values
251
+ }
252
+
253
+ public var requestId: String {
254
+ stringValue(values, "requestId") ?? ""
255
+ }
256
+
257
+ public var success: Bool {
258
+ boolValue(values, "success") ?? false
259
+ }
260
+
261
+ public var status: String {
262
+ stringValue(values, "status") ?? ""
263
+ }
264
+
265
+ public var details: String? {
266
+ stringValue(values, "details")
267
+ }
268
+
269
+ public var timestamp: Int {
270
+ intValue(values["timestamp"]) ?? Int(Date().timeIntervalSince1970 * 1000)
271
+ }
272
+
273
+ public var data: [String: Any]? {
274
+ values["data"] as? [String: Any]
275
+ }
276
+
277
+ public var description: String {
278
+ "VideoRecordingStatusEvent(requestId: \(requestId), status: \(status), success: \(success))"
152
279
  }
153
280
  }
154
281
 
@@ -158,24 +285,39 @@ public enum PhotoResponse: CustomStringConvertible, Equatable {
158
285
  case error
159
286
  }
160
287
 
161
- case success(requestId: String, uploadUrl: String, timestamp: Int)
288
+ case success(
289
+ requestId: String,
290
+ uploadUrl: String,
291
+ photoUrl: String?,
292
+ statusUrl: String?,
293
+ contentType: String?,
294
+ fileSizeBytes: Int?,
295
+ timestamp: Int
296
+ )
162
297
  case error(requestId: String, errorCode: String?, errorMessage: String, timestamp: Int)
163
298
 
164
299
  public init(values: [String: Any]) {
165
300
  let requestId = stringValue(values, "requestId") ?? ""
166
301
  let timestamp = intValue(values["timestamp"]) ?? Int(Date().timeIntervalSince1970 * 1000)
167
302
  let state = stringValue(values, "state")?.lowercased()
168
- if state == State.success.rawValue {
303
+ let success = state == State.success.rawValue || boolValue(values, "success") == true
304
+ if success {
169
305
  self = .success(
170
306
  requestId: requestId,
171
307
  uploadUrl: stringValue(values, "uploadUrl") ?? "",
308
+ photoUrl: stringValue(values, "photoUrl"),
309
+ statusUrl: stringValue(values, "statusUrl"),
310
+ contentType: stringValue(values, "contentType") ?? stringValue(values, "mimeType"),
311
+ fileSizeBytes: intValue(values["fileSizeBytes"]) ?? intValue(values["bytes"])
312
+ ?? intValue(values["size"]),
172
313
  timestamp: timestamp
173
314
  )
174
315
  } else {
175
316
  self = .error(
176
317
  requestId: requestId,
177
318
  errorCode: stringValue(values, "errorCode"),
178
- errorMessage: stringValue(values, "errorMessage") ?? "Unknown photo error",
319
+ errorMessage: stringValue(values, "errorMessage") ?? stringValue(values, "error")
320
+ ?? "Unknown photo error",
179
321
  timestamp: timestamp
180
322
  )
181
323
  }
@@ -192,27 +334,40 @@ public enum PhotoResponse: CustomStringConvertible, Equatable {
192
334
 
193
335
  public var requestId: String {
194
336
  switch self {
195
- case let .success(requestId, _, _), let .error(requestId, _, _, _):
337
+ case let .success(requestId, _, _, _, _, _, _), let .error(requestId, _, _, _):
196
338
  requestId
197
339
  }
198
340
  }
199
341
 
200
342
  public var timestamp: Int {
201
343
  switch self {
202
- case let .success(_, _, timestamp), let .error(_, _, _, timestamp):
344
+ case let .success(_, _, _, _, _, _, timestamp), let .error(_, _, _, timestamp):
203
345
  timestamp
204
346
  }
205
347
  }
206
348
 
207
349
  public var values: [String: Any] {
208
350
  switch self {
209
- case let .success(requestId, uploadUrl, timestamp):
210
- return [
351
+ case let .success(requestId, uploadUrl, photoUrl, statusUrl, contentType, fileSizeBytes, timestamp):
352
+ var values: [String: Any] = [
211
353
  "state": State.success.rawValue,
212
354
  "requestId": requestId,
213
355
  "uploadUrl": uploadUrl,
214
356
  "timestamp": timestamp,
215
357
  ]
358
+ if let photoUrl, !photoUrl.isEmpty {
359
+ values["photoUrl"] = photoUrl
360
+ }
361
+ if let statusUrl, !statusUrl.isEmpty {
362
+ values["statusUrl"] = statusUrl
363
+ }
364
+ if let contentType, !contentType.isEmpty {
365
+ values["contentType"] = contentType
366
+ }
367
+ if let fileSizeBytes {
368
+ values["fileSizeBytes"] = fileSizeBytes
369
+ }
370
+ return values
216
371
  case let .error(requestId, errorCode, errorMessage, timestamp):
217
372
  var values: [String: Any] = [
218
373
  "state": State.error.rawValue,
@@ -287,6 +442,18 @@ public struct PhotoStatusEvent: CustomStringConvertible {
287
442
  values["resolvedConfig"] as? [String: Any]
288
443
  }
289
444
 
445
+ public var requestedCaptureConfig: [String: Any]? {
446
+ values["requestedCaptureConfig"] as? [String: Any]
447
+ }
448
+
449
+ public var meteredPreview: [String: Any]? {
450
+ values["meteredPreview"] as? [String: Any]
451
+ }
452
+
453
+ public var captureMetadata: [String: Any]? {
454
+ values["captureMetadata"] as? [String: Any]
455
+ }
456
+
290
457
  public var errorCode: String? {
291
458
  stringValue(values, "errorCode")
292
459
  }
@@ -299,3 +466,45 @@ public struct PhotoStatusEvent: CustomStringConvertible {
299
466
  "PhotoStatusEvent(requestId: \(requestId), status: \(status))"
300
467
  }
301
468
  }
469
+
470
+ public struct GalleryStatusEvent: CustomStringConvertible {
471
+ public let values: [String: Any]
472
+
473
+ public init(values: [String: Any]) {
474
+ var values = values
475
+ values["type"] = "gallery_status"
476
+ self.values = values
477
+ }
478
+
479
+ public var photos: Int {
480
+ intValue(values["photos"]) ?? 0
481
+ }
482
+
483
+ public var videos: Int {
484
+ intValue(values["videos"]) ?? 0
485
+ }
486
+
487
+ public var total: Int {
488
+ intValue(values["total"]) ?? 0
489
+ }
490
+
491
+ public var totalSize: Int? {
492
+ intValue(values["totalSize"]) ?? intValue(values["total_size"])
493
+ }
494
+
495
+ public var hasContent: Bool {
496
+ boolValue(values, "hasContent", "has_content") ?? false
497
+ }
498
+
499
+ public var cameraBusy: Bool {
500
+ boolValue(values, "cameraBusy", "camera_busy") ?? false
501
+ }
502
+
503
+ public var cameraBusyReason: String? {
504
+ stringValue(values, "cameraBusyReason", "camera_busy")
505
+ }
506
+
507
+ public var description: String {
508
+ "GalleryStatusEvent(total: \(total), photos: \(photos), videos: \(videos))"
509
+ }
510
+ }
@@ -20,7 +20,7 @@ protocol ControllerManager {
20
20
 
21
21
  func requestPhoto(
22
22
  _ requestId: String, appId: String, size: String?, webhookUrl: String?, authToken: String?,
23
- compress: String?, flash: Bool, sound: Bool, exposureTimeNs: Double?, iso: Int?
23
+ compress: String?, flash: Bool, save: Bool, sound: Bool, exposureTimeNs: Double?, iso: Int?
24
24
  )
25
25
  func startStream(_ message: [String: Any])
26
26
  func stopStream()
@@ -41,7 +41,7 @@ protocol ControllerManager {
41
41
  func clearDisplay()
42
42
  func sendTextWall(_ text: String)
43
43
  func sendDoubleTextWall(_ top: String, _ bottom: String)
44
- func displayBitmap(base64ImageData: String) async -> Bool
44
+ func displayBitmap(base64ImageData: String, x: Int32?, y: Int32?, width: Int32?, height: Int32?) async -> Bool
45
45
  func showDashboard()
46
46
  func setDashboardPosition(_ height: Int, _ depth: Int)
47
47
 
@@ -154,7 +154,8 @@ extension ControllerManager {
154
154
  }
155
155
 
156
156
  var voiceActivityDetectionEnabled: Bool {
157
- DeviceStore.shared.get("glasses", "voiceActivityDetectionEnabled") as? Bool ?? true
157
+ DeviceStore.shared.get("glasses", "voiceActivityDetectionEnabled") as? Bool
158
+ ?? BluetoothSdkDefaults.voiceActivityDetectionEnabled
158
159
  }
159
160
 
160
161
  var batteryLevel: Int {
@@ -506,7 +506,7 @@ class R1: NSObject, ControllerManager {
506
506
  func sendJson(_: [String: Any], wakeUp _: Bool, requireAck _: Bool) {}
507
507
  func requestPhoto(
508
508
  _: String, appId _: String, size _: String?, webhookUrl _: String?, authToken _: String?,
509
- compress _: String?, flash _: Bool, sound _: Bool, exposureTimeNs _: Double?, iso _: Int?
509
+ compress _: String?, flash _: Bool, save _: Bool, sound _: Bool, exposureTimeNs _: Double?, iso _: Int?
510
510
  ) {}
511
511
  func startVideoRecording(requestId _: String, save _: Bool, flash _: Bool, sound _: Bool) {}
512
512
  func stopVideoRecording(requestId _: String) {}
@@ -521,7 +521,7 @@ class R1: NSObject, ControllerManager {
521
521
  func clearDisplay() {}
522
522
  func sendTextWall(_: String) {}
523
523
  func sendDoubleTextWall(_: String, _: String) {}
524
- func displayBitmap(base64ImageData _: String) async -> Bool {
524
+ func displayBitmap(base64ImageData _: String, x _: Int32? = nil, y _: Int32? = nil, width _: Int32? = nil, height _: Int32? = nil) async -> Bool {
525
525
  return false
526
526
  }
527
527
 
@@ -767,7 +767,7 @@ extension R1: CBPeripheralDelegate {
767
767
  ) {
768
768
  Bridge.log("R1: didUpdateValueFor1: \(characteristic.uuid)")
769
769
  guard let data = characteristic.value, !data.isEmpty, error == nil else { return }
770
- Bridge.log("R1: didUpdateValueFor: \(characteristic.uuid) data: \(data.toHexString())")
770
+ Bridge.log("R1: didUpdateValueFor: \(characteristic.uuid) data: \(data.hexEncodedString())")
771
771
 
772
772
  DispatchQueue.main.async { [weak self] in
773
773
  guard let self = self else { return }