@mentra/bluetooth-sdk 0.1.9 → 0.1.11

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 +195 -34
  23. package/android/src/main/java/com/mentra/bluetoothsdk/Bridge.kt +103 -21
  24. package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +210 -18
  25. package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +30 -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 +125 -11
  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 +1293 -758
  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 +197 -149
  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 +64 -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 +253 -44
  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 +37 -23
  68. package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
  69. package/build/_private/BluetoothSdkModule.js +43 -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 +25 -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 +177 -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 +68 -19
  98. package/ios/Source/DeviceManager.swift +167 -32
  99. package/ios/Source/DeviceStore.swift +24 -8
  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 +226 -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 +5 -5
  111. package/ios/Source/sgcs/G2.swift +1494 -870
  112. package/ios/Source/sgcs/Mach1.swift +4 -4
  113. package/ios/Source/sgcs/MentraLive.swift +814 -321
  114. package/ios/Source/sgcs/MentraNex.swift +604 -244
  115. package/ios/Source/sgcs/SGCManager.swift +68 -5
  116. package/ios/Source/sgcs/Simulated.swift +9 -5
  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 +344 -60
  131. package/src/_private/BluetoothSdkModule.ts +132 -102
  132. package/src/_private/photoRequestPayload.ts +28 -0
  133. package/src/index.ts +61 -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
 
@@ -11,6 +11,7 @@ public enum ButtonPhotoSize: String {
11
11
  case small
12
12
  case medium
13
13
  case large
14
+ case max
14
15
  }
15
16
 
16
17
  public enum PhotoCompression: String {
@@ -39,24 +40,101 @@ public struct ButtonVideoRecordingSettings {
39
40
  }
40
41
  }
41
42
 
43
+ public enum CameraRoiPosition: Int {
44
+ case center = 0
45
+ case bottom = 1
46
+ case top = 2
47
+
48
+ public var label: String {
49
+ switch self {
50
+ case .center:
51
+ return "center"
52
+ case .bottom:
53
+ return "bottom"
54
+ case .top:
55
+ return "top"
56
+ }
57
+ }
58
+
59
+ public static func from(rawValue: Int?) -> CameraRoiPosition {
60
+ guard let rawValue else {
61
+ return .center
62
+ }
63
+ return CameraRoiPosition(rawValue: rawValue) ?? .center
64
+ }
65
+ }
66
+
42
67
  public struct CameraFov {
43
- public static let minFov = 82
68
+ public static let minFov = 62
44
69
  public static let maxFov = 118
45
70
  public static let defaultFov = 102
46
- public static let minRoiPosition = 0
47
- public static let maxRoiPosition = 2
48
- public static let defaultRoiPosition = 0
71
+ public static let narrowFov = 82
72
+ public static let defaultRoiPosition = CameraRoiPosition.center
73
+ public static let narrow = CameraFov(
74
+ fov: CameraFov.narrowFov,
75
+ roiPosition: CameraFov.defaultRoiPosition
76
+ )
77
+ public static let standard = CameraFov(
78
+ fov: CameraFov.defaultFov,
79
+ roiPosition: CameraFov.defaultRoiPosition
80
+ )
81
+ public static let wide = CameraFov(
82
+ fov: CameraFov.maxFov,
83
+ roiPosition: CameraFov.defaultRoiPosition
84
+ )
49
85
 
50
86
  public let fov: Int
51
- public let roiPosition: Int
87
+ public let roiPosition: CameraRoiPosition
52
88
 
53
- public init(fov: Int = CameraFov.defaultFov, roiPosition: Int = CameraFov.defaultRoiPosition) {
89
+ public init(fov: Int = CameraFov.defaultFov, roiPosition: CameraRoiPosition = CameraFov.defaultRoiPosition) {
54
90
  self.fov = min(max(fov, CameraFov.minFov), CameraFov.maxFov)
55
- self.roiPosition = min(max(roiPosition, CameraFov.minRoiPosition), CameraFov.maxRoiPosition)
91
+ self.roiPosition = roiPosition
56
92
  }
57
93
 
58
94
  var value: [String: Int] {
59
- ["fov": fov, "roi_position": roiPosition]
95
+ ["fov": fov, "roi_position": roiPosition.rawValue]
96
+ }
97
+ }
98
+
99
+ public struct CameraFovResult: CustomStringConvertible {
100
+ public let requestId: String
101
+ public let fov: Int
102
+ public let roiPosition: CameraRoiPosition
103
+ public let timestamp: Int
104
+
105
+ public var values: [String: Any] {
106
+ [
107
+ "requestId": requestId,
108
+ "fov": fov,
109
+ "roiPosition": roiPosition.label,
110
+ "timestamp": timestamp,
111
+ ]
112
+ }
113
+
114
+ public var description: String {
115
+ "CameraFovResult(fov: \(fov), roiPosition: \(roiPosition.label))"
116
+ }
117
+
118
+ static func from(ack: SettingsAckEvent, fallback: CameraFov) throws -> CameraFovResult {
119
+ if ack.status == "error" {
120
+ throw BluetoothError(
121
+ code: ack.errorCode ?? "camera_fov_failed",
122
+ message: ack.errorMessage ?? "Camera FOV request failed."
123
+ )
124
+ }
125
+ if !ack.hardwareApplied {
126
+ throw BluetoothError(
127
+ code: "camera_fov_not_applied",
128
+ message: "Camera FOV was saved but not applied to hardware."
129
+ )
130
+ }
131
+
132
+ return CameraFovResult(
133
+ requestId: ack.requestId,
134
+ fov: ack.fov ?? fallback.fov,
135
+ roiPosition: CameraRoiPosition.from(rawValue: ack.roiPosition ?? fallback.roiPosition.rawValue),
136
+ timestamp: ack.timestamp
137
+ )
60
138
  }
61
139
  }
62
140
 
@@ -68,6 +146,7 @@ public struct PhotoRequest {
68
146
  public let authToken: String?
69
147
  public let compress: PhotoCompression?
70
148
  public let flash: Bool
149
+ public let save: Bool
71
150
  public let sound: Bool
72
151
  /// Sensor exposure time for this capture only (ns), or nil for auto exposure
73
152
  public let exposureTimeNs: Double?
@@ -82,6 +161,7 @@ public struct PhotoRequest {
82
161
  authToken: String? = nil,
83
162
  compress: PhotoCompression? = nil,
84
163
  flash: Bool = true,
164
+ save: Bool = false,
85
165
  sound: Bool,
86
166
  exposureTimeNs: Double? = nil,
87
167
  iso: Int? = nil
@@ -93,6 +173,7 @@ public struct PhotoRequest {
93
173
  self.authToken = authToken
94
174
  self.compress = compress
95
175
  self.flash = flash
176
+ self.save = save
96
177
  self.sound = sound
97
178
  self.exposureTimeNs = exposureTimeNs
98
179
  self.iso = iso
@@ -144,11 +225,58 @@ public struct VideoRecordingRequest {
144
225
  public let requestId: String
145
226
  public let save: Bool
146
227
  public let sound: Bool
228
+ // Optional per-recording overrides; 0 means "use the saved button-video default".
229
+ public let width: Int
230
+ public let height: Int
231
+ public let fps: Int
147
232
 
148
- public init(requestId: String, save: Bool, sound: Bool) {
233
+ public init(
234
+ requestId: String, save: Bool, sound: Bool, width: Int = 0, height: Int = 0, fps: Int = 0
235
+ ) {
149
236
  self.requestId = requestId
150
237
  self.save = save
151
238
  self.sound = sound
239
+ self.width = width
240
+ self.height = height
241
+ self.fps = fps
242
+ }
243
+ }
244
+
245
+ public struct VideoRecordingStatusEvent: CustomStringConvertible {
246
+ public let values: [String: Any]
247
+
248
+ public init(values: [String: Any]) {
249
+ var values = values
250
+ values["type"] = "video_recording_status"
251
+ self.values = values
252
+ }
253
+
254
+ public var requestId: String {
255
+ stringValue(values, "requestId") ?? ""
256
+ }
257
+
258
+ public var success: Bool {
259
+ boolValue(values, "success") ?? false
260
+ }
261
+
262
+ public var status: String {
263
+ stringValue(values, "status") ?? ""
264
+ }
265
+
266
+ public var details: String? {
267
+ stringValue(values, "details")
268
+ }
269
+
270
+ public var timestamp: Int {
271
+ intValue(values["timestamp"]) ?? Int(Date().timeIntervalSince1970 * 1000)
272
+ }
273
+
274
+ public var data: [String: Any]? {
275
+ values["data"] as? [String: Any]
276
+ }
277
+
278
+ public var description: String {
279
+ "VideoRecordingStatusEvent(requestId: \(requestId), status: \(status), success: \(success))"
152
280
  }
153
281
  }
154
282
 
@@ -158,24 +286,39 @@ public enum PhotoResponse: CustomStringConvertible, Equatable {
158
286
  case error
159
287
  }
160
288
 
161
- case success(requestId: String, uploadUrl: String, timestamp: Int)
289
+ case success(
290
+ requestId: String,
291
+ uploadUrl: String,
292
+ photoUrl: String?,
293
+ statusUrl: String?,
294
+ contentType: String?,
295
+ fileSizeBytes: Int?,
296
+ timestamp: Int
297
+ )
162
298
  case error(requestId: String, errorCode: String?, errorMessage: String, timestamp: Int)
163
299
 
164
300
  public init(values: [String: Any]) {
165
301
  let requestId = stringValue(values, "requestId") ?? ""
166
302
  let timestamp = intValue(values["timestamp"]) ?? Int(Date().timeIntervalSince1970 * 1000)
167
303
  let state = stringValue(values, "state")?.lowercased()
168
- if state == State.success.rawValue {
304
+ let success = state == State.success.rawValue || boolValue(values, "success") == true
305
+ if success {
169
306
  self = .success(
170
307
  requestId: requestId,
171
308
  uploadUrl: stringValue(values, "uploadUrl") ?? "",
309
+ photoUrl: stringValue(values, "photoUrl"),
310
+ statusUrl: stringValue(values, "statusUrl"),
311
+ contentType: stringValue(values, "contentType") ?? stringValue(values, "mimeType"),
312
+ fileSizeBytes: intValue(values["fileSizeBytes"]) ?? intValue(values["bytes"])
313
+ ?? intValue(values["size"]),
172
314
  timestamp: timestamp
173
315
  )
174
316
  } else {
175
317
  self = .error(
176
318
  requestId: requestId,
177
319
  errorCode: stringValue(values, "errorCode"),
178
- errorMessage: stringValue(values, "errorMessage") ?? "Unknown photo error",
320
+ errorMessage: stringValue(values, "errorMessage") ?? stringValue(values, "error")
321
+ ?? "Unknown photo error",
179
322
  timestamp: timestamp
180
323
  )
181
324
  }
@@ -192,27 +335,40 @@ public enum PhotoResponse: CustomStringConvertible, Equatable {
192
335
 
193
336
  public var requestId: String {
194
337
  switch self {
195
- case let .success(requestId, _, _), let .error(requestId, _, _, _):
338
+ case let .success(requestId, _, _, _, _, _, _), let .error(requestId, _, _, _):
196
339
  requestId
197
340
  }
198
341
  }
199
342
 
200
343
  public var timestamp: Int {
201
344
  switch self {
202
- case let .success(_, _, timestamp), let .error(_, _, _, timestamp):
345
+ case let .success(_, _, _, _, _, _, timestamp), let .error(_, _, _, timestamp):
203
346
  timestamp
204
347
  }
205
348
  }
206
349
 
207
350
  public var values: [String: Any] {
208
351
  switch self {
209
- case let .success(requestId, uploadUrl, timestamp):
210
- return [
352
+ case let .success(requestId, uploadUrl, photoUrl, statusUrl, contentType, fileSizeBytes, timestamp):
353
+ var values: [String: Any] = [
211
354
  "state": State.success.rawValue,
212
355
  "requestId": requestId,
213
356
  "uploadUrl": uploadUrl,
214
357
  "timestamp": timestamp,
215
358
  ]
359
+ if let photoUrl, !photoUrl.isEmpty {
360
+ values["photoUrl"] = photoUrl
361
+ }
362
+ if let statusUrl, !statusUrl.isEmpty {
363
+ values["statusUrl"] = statusUrl
364
+ }
365
+ if let contentType, !contentType.isEmpty {
366
+ values["contentType"] = contentType
367
+ }
368
+ if let fileSizeBytes {
369
+ values["fileSizeBytes"] = fileSizeBytes
370
+ }
371
+ return values
216
372
  case let .error(requestId, errorCode, errorMessage, timestamp):
217
373
  var values: [String: Any] = [
218
374
  "state": State.error.rawValue,
@@ -287,6 +443,18 @@ public struct PhotoStatusEvent: CustomStringConvertible {
287
443
  values["resolvedConfig"] as? [String: Any]
288
444
  }
289
445
 
446
+ public var requestedCaptureConfig: [String: Any]? {
447
+ values["requestedCaptureConfig"] as? [String: Any]
448
+ }
449
+
450
+ public var meteredPreview: [String: Any]? {
451
+ values["meteredPreview"] as? [String: Any]
452
+ }
453
+
454
+ public var captureMetadata: [String: Any]? {
455
+ values["captureMetadata"] as? [String: Any]
456
+ }
457
+
290
458
  public var errorCode: String? {
291
459
  stringValue(values, "errorCode")
292
460
  }
@@ -299,3 +467,45 @@ public struct PhotoStatusEvent: CustomStringConvertible {
299
467
  "PhotoStatusEvent(requestId: \(requestId), status: \(status))"
300
468
  }
301
469
  }
470
+
471
+ public struct GalleryStatusEvent: CustomStringConvertible {
472
+ public let values: [String: Any]
473
+
474
+ public init(values: [String: Any]) {
475
+ var values = values
476
+ values["type"] = "gallery_status"
477
+ self.values = values
478
+ }
479
+
480
+ public var photos: Int {
481
+ intValue(values["photos"]) ?? 0
482
+ }
483
+
484
+ public var videos: Int {
485
+ intValue(values["videos"]) ?? 0
486
+ }
487
+
488
+ public var total: Int {
489
+ intValue(values["total"]) ?? 0
490
+ }
491
+
492
+ public var totalSize: Int? {
493
+ intValue(values["totalSize"]) ?? intValue(values["total_size"])
494
+ }
495
+
496
+ public var hasContent: Bool {
497
+ boolValue(values, "hasContent", "has_content") ?? false
498
+ }
499
+
500
+ public var cameraBusy: Bool {
501
+ boolValue(values, "cameraBusy", "camera_busy") ?? false
502
+ }
503
+
504
+ public var cameraBusyReason: String? {
505
+ stringValue(values, "cameraBusyReason", "camera_busy")
506
+ }
507
+
508
+ public var description: String {
509
+ "GalleryStatusEvent(total: \(total), photos: \(photos), videos: \(videos))"
510
+ }
511
+ }
@@ -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):