@mentra/bluetooth-sdk 0.1.9 → 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 +6 -0
  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
@@ -58,6 +58,12 @@ class ObservableStore {
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 }
@@ -49,7 +49,8 @@ public struct VoiceActivityDetectionStatusEvent: CustomStringConvertible {
49
49
  public let values: [String: Any]
50
50
 
51
51
  public init(values: [String: Any]) {
52
- voiceActivityDetectionEnabled = boolValue(values, "voiceActivityDetectionEnabled") ?? true
52
+ voiceActivityDetectionEnabled =
53
+ boolValue(values, "voiceActivityDetectionEnabled") ?? BluetoothSdkDefaults.voiceActivityDetectionEnabled
53
54
  self.values = values
54
55
  }
55
56
 
@@ -72,6 +73,166 @@ public struct SpeakingStatusEvent: CustomStringConvertible {
72
73
  }
73
74
  }
74
75
 
76
+ public struct OtaUpdateAvailableEvent: CustomStringConvertible {
77
+ public let versionCode: Int?
78
+ public let versionName: String?
79
+ public let updates: [String]
80
+ public let totalSize: Int?
81
+ public let cacheReady: Bool?
82
+ public let values: [String: Any]
83
+
84
+ public init(values: [String: Any]) {
85
+ versionCode = intValue(values["version_code"])
86
+ versionName = stringValue(values, "version_name")
87
+ updates = values["updates"] as? [String] ?? []
88
+ totalSize = intValue(values["total_size"])
89
+ cacheReady = boolValue(values, "cache_ready")
90
+ self.values = values
91
+ }
92
+
93
+ public var description: String {
94
+ "OtaUpdateAvailableEvent(versionName: \(versionName ?? "unknown"), updates: \(updates.joined(separator: ",")))"
95
+ }
96
+ }
97
+
98
+ public struct OtaStartAckEvent: CustomStringConvertible {
99
+ public let timestamp: Int?
100
+ public let values: [String: Any]
101
+
102
+ public init(values: [String: Any]) {
103
+ timestamp = intValue(values["timestamp"])
104
+ self.values = values
105
+ }
106
+
107
+ public var description: String {
108
+ "OtaStartAckEvent(timestamp: \(timestamp.map(String.init) ?? "unknown"))"
109
+ }
110
+ }
111
+
112
+ public struct OtaStatusEvent: CustomStringConvertible {
113
+ public let sessionId: String
114
+ public let totalSteps: Int
115
+ public let currentStep: Int
116
+ public let stepType: String
117
+ public let phase: String
118
+ public let stepPercent: Int
119
+ public let overallPercent: Int
120
+ public let status: String
121
+ public let errorMessage: String?
122
+ public let glassesTimeMs: Int?
123
+ public let values: [String: Any]
124
+
125
+ public init(values: [String: Any]) {
126
+ sessionId = stringValue(values, "session_id") ?? ""
127
+ totalSteps = intValue(values["total_steps"]) ?? 0
128
+ currentStep = intValue(values["current_step"]) ?? 0
129
+ stepType = stringValue(values, "step_type") ?? ""
130
+ phase = stringValue(values, "phase") ?? ""
131
+ stepPercent = intValue(values["step_percent"]) ?? 0
132
+ overallPercent = intValue(values["overall_percent"]) ?? 0
133
+ status = stringValue(values, "status") ?? ""
134
+ errorMessage = stringValue(values, "error_message")
135
+ glassesTimeMs = intValue(values["glasses_time_ms"])
136
+ self.values = values
137
+ }
138
+
139
+ public var description: String {
140
+ "OtaStatusEvent(status: \(status), overallPercent: \(overallPercent))"
141
+ }
142
+ }
143
+
144
+ public struct OtaQueryResult: CustomStringConvertible {
145
+ public let values: [String: Any]
146
+
147
+ public init(values: [String: Any]) {
148
+ self.values = values
149
+ }
150
+
151
+ public var type: String {
152
+ stringValue(values, "type") ?? ""
153
+ }
154
+
155
+ public var status: String? {
156
+ stringValue(values, "status")
157
+ }
158
+
159
+ public var description: String {
160
+ "OtaQueryResult(type: \(type), status: \(status ?? "unknown"))"
161
+ }
162
+ }
163
+
164
+ public struct SettingsAckEvent: CustomStringConvertible {
165
+ public let values: [String: Any]
166
+
167
+ public init(values: [String: Any]) {
168
+ self.values = values
169
+ }
170
+
171
+ public var requestId: String {
172
+ stringValue(values, "requestId") ?? ""
173
+ }
174
+
175
+ public var setting: String {
176
+ stringValue(values, "setting") ?? ""
177
+ }
178
+
179
+ public var status: String {
180
+ stringValue(values, "status") ?? "applied"
181
+ }
182
+
183
+ public var timestamp: Int {
184
+ intValue(values["timestamp"]) ?? Int(Date().timeIntervalSince1970 * 1000)
185
+ }
186
+
187
+ public var fov: Int? {
188
+ intValue(values["fov"])
189
+ }
190
+
191
+ public var roiPosition: Int? {
192
+ intValue(values["roiPosition"]) ?? intValue(values["roi_position"])
193
+ }
194
+
195
+ public var hardwareApplied: Bool {
196
+ boolValue(values, "hardwareApplied") ?? boolValue(values, "hardware_applied") ?? false
197
+ }
198
+
199
+ public var errorCode: String? {
200
+ stringValue(values, "errorCode")
201
+ }
202
+
203
+ public var errorMessage: String? {
204
+ stringValue(values, "errorMessage")
205
+ }
206
+
207
+ public var description: String {
208
+ "SettingsAckEvent(setting: \(setting), status: \(status))"
209
+ }
210
+ }
211
+
212
+ public struct RgbLedControlResponseEvent: CustomStringConvertible {
213
+ public let values: [String: Any]
214
+
215
+ public init(values: [String: Any]) {
216
+ self.values = values
217
+ }
218
+
219
+ public var requestId: String {
220
+ stringValue(values, "requestId") ?? ""
221
+ }
222
+
223
+ public var state: String {
224
+ stringValue(values, "state") ?? "error"
225
+ }
226
+
227
+ public var errorCode: String? {
228
+ stringValue(values, "errorCode")
229
+ }
230
+
231
+ public var description: String {
232
+ "RgbLedControlResponseEvent(requestId: \(requestId), state: \(state))"
233
+ }
234
+ }
235
+
75
236
  public enum BluetoothEvent: CustomStringConvertible {
76
237
  case buttonPress(ButtonPressEvent)
77
238
  case touch(TouchEvent)
@@ -82,8 +243,15 @@ public enum BluetoothEvent: CustomStringConvertible {
82
243
  case hotspotError(HotspotErrorEvent)
83
244
  case photoResponse(PhotoResponseEvent)
84
245
  case photoStatus(PhotoStatusEvent)
246
+ case videoRecordingStatus(VideoRecordingStatusEvent)
247
+ case rgbLedControlResponse(RgbLedControlResponseEvent)
85
248
  case streamStatus(StreamStatusEvent)
86
249
  case keepAliveAck(KeepAliveAckEvent)
250
+ case otaUpdateAvailable(OtaUpdateAvailableEvent)
251
+ case otaStartAck(OtaStartAckEvent)
252
+ case otaStatus(OtaStatusEvent)
253
+ case settingsAck(SettingsAckEvent)
254
+ case versionInfo(VersionInfoResult)
87
255
  case localTranscription(LocalTranscriptionEvent)
88
256
  case raw(name: String, values: [String: Any])
89
257
 
@@ -107,10 +275,24 @@ public enum BluetoothEvent: CustomStringConvertible {
107
275
  event.description
108
276
  case let .photoStatus(event):
109
277
  event.description
278
+ case let .videoRecordingStatus(event):
279
+ event.description
280
+ case let .rgbLedControlResponse(event):
281
+ event.description
110
282
  case let .streamStatus(event):
111
283
  event.description
112
284
  case let .keepAliveAck(event):
113
285
  event.description
286
+ case let .otaUpdateAvailable(event):
287
+ event.description
288
+ case let .otaStartAck(event):
289
+ event.description
290
+ case let .otaStatus(event):
291
+ event.description
292
+ case let .settingsAck(event):
293
+ event.description
294
+ case let .versionInfo(event):
295
+ event.description
114
296
  case let .localTranscription(event):
115
297
  event.description
116
298
  case let .raw(name, values):