@mentra/bluetooth-sdk 0.1.13 → 0.1.16

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 (80) hide show
  1. package/README.md +18 -8
  2. package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +48 -48
  3. package/android/src/main/java/com/mentra/bluetoothsdk/Bridge.kt +5 -20
  4. package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +360 -269
  5. package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +3 -7
  6. package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +152 -72
  7. package/android/src/main/java/com/mentra/bluetoothsdk/OtaManifest.kt +12 -12
  8. package/android/src/main/java/com/mentra/bluetoothsdk/camera/CameraModels.kt +25 -11
  9. package/android/src/main/java/com/mentra/bluetoothsdk/controllers/ControllerManager.kt +1 -2
  10. package/android/src/main/java/com/mentra/bluetoothsdk/controllers/R1.kt +1 -2
  11. package/android/src/main/java/com/mentra/bluetoothsdk/events/BluetoothEvents.kt +1 -22
  12. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G1.kt +51 -8
  13. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G2.kt +1184 -800
  14. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Mach1.kt +1 -5
  15. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.kt +423 -117
  16. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraNex.kt +4 -6
  17. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Nimo.kt +2532 -0
  18. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/SGCManager.kt +17 -5
  19. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Simulated.kt +3 -7
  20. package/android/src/main/java/com/mentra/bluetoothsdk/status/DeviceStatus.kt +0 -6
  21. package/android/src/main/java/com/mentra/bluetoothsdk/streaming/StreamModels.kt +16 -33
  22. package/android/src/main/java/com/mentra/bluetoothsdk/types/DeviceModels.kt +5 -3
  23. package/android/src/main/java/com/mentra/bluetoothsdk/utils/Constants.kt +2 -1
  24. package/android/src/test/java/com/mentra/bluetoothsdk/camera/PhotoRequestTest.kt +60 -1
  25. package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/NimoProtocolTest.kt +333 -0
  26. package/build/BluetoothSdk.types.d.ts +50 -24
  27. package/build/BluetoothSdk.types.d.ts.map +1 -1
  28. package/build/BluetoothSdk.types.js +1 -0
  29. package/build/BluetoothSdk.types.js.map +1 -1
  30. package/build/_internal.d.ts +4 -4
  31. package/build/_internal.js +4 -4
  32. package/build/_internal.js.map +1 -1
  33. package/build/_private/BluetoothSdkModule.d.ts +14 -8
  34. package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
  35. package/build/_private/BluetoothSdkModule.js +4 -3
  36. package/build/_private/BluetoothSdkModule.js.map +1 -1
  37. package/build/_private/cameraRequestPayload.d.ts +4 -0
  38. package/build/_private/cameraRequestPayload.d.ts.map +1 -0
  39. package/build/_private/cameraRequestPayload.js +25 -0
  40. package/build/_private/cameraRequestPayload.js.map +1 -0
  41. package/build/_private/photoRequestPayload.d.ts.map +1 -1
  42. package/build/_private/photoRequestPayload.js +11 -3
  43. package/build/_private/photoRequestPayload.js.map +1 -1
  44. package/build/index.d.ts +1 -1
  45. package/build/index.d.ts.map +1 -1
  46. package/build/index.js +70 -61
  47. package/build/index.js.map +1 -1
  48. package/ios/BluetoothSdkModule.swift +51 -28
  49. package/ios/Source/Bridge.swift +4 -16
  50. package/ios/Source/DeviceManager.swift +76 -22
  51. package/ios/Source/DeviceStore.swift +0 -4
  52. package/ios/Source/MentraBluetoothSDK.swift +144 -78
  53. package/ios/Source/OtaManifest.swift +13 -13
  54. package/ios/Source/camera/CameraModels.swift +82 -20
  55. package/ios/Source/controllers/ControllerManager.swift +1 -2
  56. package/ios/Source/controllers/R1.swift +1 -2
  57. package/ios/Source/errors/{BluetoothError.swift → BluetoothSdkError.swift} +1 -1
  58. package/ios/Source/events/BluetoothEvents.swift +5 -27
  59. package/ios/Source/internal/BluetoothAvailability.swift +5 -5
  60. package/ios/Source/sgcs/Frame.swift +0 -2
  61. package/ios/Source/sgcs/G1.swift +52 -4
  62. package/ios/Source/sgcs/G2.swift +643 -204
  63. package/ios/Source/sgcs/Mach1.swift +1 -3
  64. package/ios/Source/sgcs/MentraLive.swift +64 -77
  65. package/ios/Source/sgcs/MentraNex.swift +1 -3
  66. package/ios/Source/sgcs/Nimo.swift +1928 -0
  67. package/ios/Source/sgcs/SGCManager.swift +16 -10
  68. package/ios/Source/sgcs/Simulated.swift +2 -6
  69. package/ios/Source/status/DeviceStatus.swift +0 -8
  70. package/ios/Source/streaming/StreamModels.swift +7 -32
  71. package/ios/Source/stt/STTTools.swift +1 -1
  72. package/ios/Source/types/DeviceModels.swift +5 -0
  73. package/ios/Source/utils/Constants.swift +2 -0
  74. package/package.json +6 -1
  75. package/src/BluetoothSdk.types.ts +54 -28
  76. package/src/_internal.ts +4 -4
  77. package/src/_private/BluetoothSdkModule.ts +20 -11
  78. package/src/_private/cameraRequestPayload.ts +29 -0
  79. package/src/_private/photoRequestPayload.ts +12 -3
  80. package/src/index.ts +77 -67
@@ -22,13 +22,13 @@ protocol SGCManager {
22
22
  func startStream(_ message: [String: Any])
23
23
  func stopStream()
24
24
  func sendStreamKeepAlive(_ message: [String: Any])
25
- func startVideoRecording(requestId: String, save: Bool, flash: Bool, sound: Bool)
25
+ func startVideoRecording(requestId: String, save: Bool, sound: Bool)
26
26
  /// Start video recording with optional per-recording resolution/fps. A width,
27
27
  /// height, or fps of 0 means "use the device's saved button-video default".
28
28
  /// Defaulted in an extension to delegate to the basic recording path; devices
29
29
  /// that support custom settings (e.g. Mentra Live) override this.
30
30
  func startVideoRecording(
31
- requestId: String, save: Bool, flash: Bool, sound: Bool, width: Int, height: Int, fps: Int,
31
+ requestId: String, save: Bool, sound: Bool, width: Int, height: Int, fps: Int,
32
32
  maxRecordingTimeMinutes: Int
33
33
  )
34
34
  func stopVideoRecording(requestId: String)
@@ -44,7 +44,6 @@ protocol SGCManager {
44
44
  func sendButtonPhotoSettings()
45
45
  func sendButtonVideoRecordingSettings()
46
46
  func sendButtonMaxRecordingTime()
47
- func sendButtonCameraLedSetting()
48
47
  func sendCameraFovSetting()
49
48
 
50
49
  // MARK: - Display Control
@@ -57,6 +56,12 @@ protocol SGCManager {
57
56
  /// Display a bitmap. Optional `x`/`y`/`width`/`height` position and size the target
58
57
  /// container (used by G2; other SGCs ignore positioning and render the bitmap as before).
59
58
  func displayBitmap(base64ImageData: String, x: Int32?, y: Int32?, width: Int32?, height: Int32?) async -> Bool
59
+ /// Show text in a positioned container with an optional rounded border.
60
+ /// G2-only capability; default no-op (see protocol extension) so other glasses ignore it.
61
+ func sendPositionedText(
62
+ _ text: String, x: Int32, y: Int32, width: Int32, height: Int32,
63
+ borderWidth: Int32, borderRadius: Int32
64
+ ) async
60
65
  func showDashboard()
61
66
  func setDashboardPosition(_ height: Int, _ depth: Int)
62
67
  /// Default implementation sends both via [setDashboardPosition]; Nex overrides to one protobuf.
@@ -119,7 +124,6 @@ protocol SGCManager {
119
124
  func sendOtaStart(otaVersionUrl: String?)
120
125
  func sendOtaQueryStatus()
121
126
  func sendSetSystemTime(_ timestampMs: Int64)
122
- func sendOtaRetryVersionCheck()
123
127
 
124
128
  // MARK: - User Context (for crash reporting)
125
129
 
@@ -146,13 +150,19 @@ protocol SGCManager {
146
150
  /// doesn't seem to work for concurrency reasons :(
147
151
  /// we can make read-only getters for convienence though:
148
152
  extension SGCManager {
153
+ /// Default: no-op. Only G2 renders positioned text containers; other glasses ignore it.
154
+ func sendPositionedText(
155
+ _: String, x _: Int32, y _: Int32, width _: Int32, height _: Int32,
156
+ borderWidth _: Int32, borderRadius _: Int32
157
+ ) async {}
158
+
149
159
  // MARK: - Video recording (default: ignore custom settings, use saved defaults)
150
160
 
151
161
  func startVideoRecording(
152
- requestId: String, save: Bool, flash: Bool, sound: Bool, width _: Int, height _: Int,
162
+ requestId: String, save: Bool, sound: Bool, width _: Int, height _: Int,
153
163
  fps _: Int, maxRecordingTimeMinutes _: Int
154
164
  ) {
155
- startVideoRecording(requestId: requestId, save: save, flash: flash, sound: sound)
165
+ startVideoRecording(requestId: requestId, save: save, sound: sound)
156
166
  }
157
167
 
158
168
  func stopVideoRecording(requestId: String, webhookUrl _: String?, authToken _: String?) {
@@ -202,10 +212,6 @@ extension SGCManager {
202
212
  Bridge.log("SGC: sendSetSystemTime not supported")
203
213
  }
204
214
 
205
- func sendOtaRetryVersionCheck() {
206
- Bridge.log("SGC: sendOtaRetryVersionCheck not supported")
207
- }
208
-
209
215
  // MARK: - Default DeviceStore-backed property implementations
210
216
 
211
217
  var fullyBooted: Bool {
@@ -80,7 +80,7 @@ class Simulated: SGCManager {
80
80
  // MARK: - Camera & Media
81
81
 
82
82
  func requestPhoto(_ request: PhotoRequest) {
83
- Bridge.log("requestPhoto flash=\(request.flash) save=\(request.save) sound=\(request.sound)")
83
+ Bridge.log("requestPhoto save=\(request.save) sound=\(request.sound)")
84
84
  }
85
85
 
86
86
  func startStream(_: [String: Any]) {
@@ -95,7 +95,7 @@ class Simulated: SGCManager {
95
95
  Bridge.log("sendStreamKeepAlive")
96
96
  }
97
97
 
98
- func startVideoRecording(requestId _: String, save _: Bool, flash _: Bool, sound _: Bool) {
98
+ func startVideoRecording(requestId _: String, save _: Bool, sound _: Bool) {
99
99
  Bridge.log("startVideoRecording")
100
100
  }
101
101
 
@@ -113,10 +113,6 @@ class Simulated: SGCManager {
113
113
  Bridge.log("sendButtonVideoRecordingSettings")
114
114
  }
115
115
 
116
- func sendButtonCameraLedSetting() {
117
- Bridge.log("sendButtonCameraLedSetting")
118
- }
119
-
120
116
  func sendCameraFovSetting() {
121
117
  Bridge.log("sendCameraFovSetting")
122
118
  }
@@ -552,10 +552,6 @@ struct BluetoothStatus: CustomStringConvertible {
552
552
  ButtonPhotoSize(rawValue: stringValue(values, "button_photo_size") ?? "") ?? .medium
553
553
  }
554
554
 
555
- var buttonCameraLed: Bool {
556
- boolValue(values, "button_camera_led") ?? true
557
- }
558
-
559
555
  var buttonMaxRecordingTime: Int {
560
556
  intValue(values["button_max_recording_time"]) ?? 10
561
557
  }
@@ -924,10 +920,6 @@ struct BluetoothStatusUpdate: CustomStringConvertible {
924
920
  optionalStringValue(values, "button_photo_size").map { ButtonPhotoSize(normalizedRawValue: $0) }
925
921
  }
926
922
 
927
- var buttonCameraLed: Bool? {
928
- optionalBoolValue(values, "button_camera_led")
929
- }
930
-
931
923
  var buttonMaxRecordingTime: Int? {
932
924
  optionalIntValue(values, "button_max_recording_time")
933
925
  }
@@ -23,6 +23,7 @@ public struct StreamVideoConfig {
23
23
  if let width { values["width"] = width }
24
24
  if let height { values["height"] = height }
25
25
  if let bitrate { values["bitrate"] = bitrate }
26
+ // ASG stream parsers shipped with the BLE key named "frameRate".
26
27
  if let fps { values["frameRate"] = fps }
27
28
  return values
28
29
  }
@@ -219,31 +220,22 @@ public struct StreamResolvedConfig: Equatable {
219
220
  public struct StreamRequest {
220
221
  public let streamUrl: String
221
222
  public let streamId: String
222
- public let keepAlive: Bool
223
- public let keepAliveIntervalSeconds: Int
224
223
  public let sound: Bool
225
224
  public let video: StreamVideoConfig?
226
225
  public let audio: StreamAudioConfig?
227
- public let extraValues: [String: Any]
228
226
 
229
227
  public init(
230
228
  streamUrl: String,
231
229
  streamId: String = "",
232
- keepAlive: Bool = true,
233
- keepAliveIntervalSeconds: Int = 5,
234
230
  sound: Bool = true,
235
231
  video: StreamVideoConfig? = nil,
236
- audio: StreamAudioConfig? = nil,
237
- extraValues: [String: Any] = [:]
232
+ audio: StreamAudioConfig? = nil
238
233
  ) {
239
234
  self.streamUrl = streamUrl
240
235
  self.streamId = streamId
241
- self.keepAlive = keepAlive
242
- self.keepAliveIntervalSeconds = keepAliveIntervalSeconds
243
236
  self.sound = sound
244
237
  self.video = video
245
238
  self.audio = audio
246
- self.extraValues = extraValues
247
239
  }
248
240
 
249
241
  init(values: [String: Any]) {
@@ -254,25 +246,17 @@ public struct StreamRequest {
254
246
  ?? values["whipUrl"] as? String
255
247
  ?? "",
256
248
  streamId: values["streamId"] as? String ?? "",
257
- keepAlive: values["keepAlive"] as? Bool ?? true,
258
- keepAliveIntervalSeconds: intValue(values["keepAliveIntervalSeconds"]) ?? 5,
259
249
  sound: values["sound"] as? Bool ?? true,
260
250
  video: StreamVideoConfig(values: values["video"] as? [String: Any]),
261
- audio: StreamAudioConfig(values: values["audio"] as? [String: Any]),
262
- extraValues: values
251
+ audio: StreamAudioConfig(values: values["audio"] as? [String: Any])
263
252
  )
264
253
  }
265
254
 
266
255
  public var values: [String: Any] {
267
- var values = extraValues
268
- values.removeValue(forKey: "keepAliveMode")
256
+ var values: [String: Any] = [:]
269
257
  values["type"] = "start_stream"
270
258
  values["streamUrl"] = streamUrl
271
259
  values["streamId"] = streamId
272
- values["keepAlive"] = keepAlive
273
- values["keepAliveIntervalSeconds"] = keepAliveIntervalSeconds
274
- // The camera light is a privacy indicator and cannot be disabled by SDK callers.
275
- values["flash"] = true
276
260
  values["sound"] = sound
277
261
  if let videoValues = video?.dictionary, !videoValues.isEmpty {
278
262
  values["video"] = videoValues
@@ -284,33 +268,24 @@ public struct StreamRequest {
284
268
  }
285
269
  }
286
270
 
287
- extension StreamRequest {
288
- var isExternallyManagedKeepAlive: Bool {
289
- stringValue(extraValues, "keepAliveMode") == "external"
290
- }
291
- }
292
-
293
271
  struct StreamKeepAliveRequest {
294
272
  let streamId: String
295
273
  let ackId: String
296
- let extraValues: [String: Any]
297
274
 
298
- init(streamId: String, ackId: String, extraValues: [String: Any] = [:]) {
275
+ init(streamId: String, ackId: String) {
299
276
  self.streamId = streamId
300
277
  self.ackId = ackId
301
- self.extraValues = extraValues
302
278
  }
303
279
 
304
280
  init(values: [String: Any]) {
305
281
  self.init(
306
282
  streamId: values["streamId"] as? String ?? "",
307
- ackId: values["ackId"] as? String ?? "",
308
- extraValues: values
283
+ ackId: values["ackId"] as? String ?? ""
309
284
  )
310
285
  }
311
286
 
312
287
  var values: [String: Any] {
313
- var values = extraValues
288
+ var values: [String: Any] = [:]
314
289
  values["type"] = "keep_stream_alive"
315
290
  values["streamId"] = streamId
316
291
  values["ackId"] = ackId
@@ -7,7 +7,7 @@ class STTTools {
7
7
  // Send partial result to server witgetConnectedBluetoothNameh proper formatting
8
8
  let transcriptionLanguage =
9
9
  UserDefaults.standard.string(forKey: "STTModelLanguageCode") ?? "en-US"
10
- Bridge.log("Mentra: Sending partial transcription: \(text), \(transcriptionLanguage)")
10
+ // Bridge.log("Mentra: Sending partial transcription: \(text), \(transcriptionLanguage)")
11
11
  let transcription: [String: Any] = [
12
12
  "type": "local_transcription",
13
13
  "text": transcriptionLanguage == "en-US" ? text.lowercased() : text,
@@ -18,6 +18,7 @@ public enum DeviceModel: String {
18
18
  case mach1
19
19
  case z100
20
20
  case frame
21
+ case nimo
21
22
  case simulated
22
23
  case r1
23
24
 
@@ -37,6 +38,8 @@ public enum DeviceModel: String {
37
38
  DeviceTypes.Z100
38
39
  case .frame:
39
40
  DeviceTypes.FRAME
41
+ case .nimo:
42
+ DeviceTypes.NIMO
40
43
  case .simulated:
41
44
  DeviceTypes.SIMULATED
42
45
  case .r1:
@@ -60,6 +63,8 @@ public enum DeviceModel: String {
60
63
  .z100
61
64
  case DeviceTypes.FRAME:
62
65
  .frame
66
+ case DeviceTypes.NIMO:
67
+ .nimo
63
68
  case DeviceTypes.SIMULATED:
64
69
  .simulated
65
70
  case ControllerTypes.R1:
@@ -7,6 +7,7 @@ struct DeviceTypes {
7
7
  static let Z100 = "Vuzix Z100"
8
8
  static let NEX = "Mentra Display"
9
9
  static let FRAME = "Brilliant Frame"
10
+ static let NIMO = "NIMO"
10
11
 
11
12
  static let ALL = [
12
13
  SIMULATED,
@@ -17,6 +18,7 @@ struct DeviceTypes {
17
18
  Z100,
18
19
  NEX,
19
20
  FRAME,
21
+ NIMO,
20
22
  ]
21
23
 
22
24
  /// Private init to prevent instantiation
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mentra/bluetooth-sdk",
3
- "version": "0.1.13",
3
+ "version": "0.1.16",
4
4
  "description": "SDK for communicating with smart glasses",
5
5
  "main": "build/index.js",
6
6
  "react-native": "src/index.ts",
@@ -12,6 +12,11 @@
12
12
  "react-native": "./src/index.ts",
13
13
  "default": "./build/index.js"
14
14
  },
15
+ "./internal": {
16
+ "types": "./build/_internal.d.ts",
17
+ "react-native": "./src/_internal.ts",
18
+ "default": "./build/_internal.js"
19
+ },
15
20
  "./react": {
16
21
  "types": "./build/react/index.d.ts",
17
22
  "react-native": "./src/react/index.ts",
@@ -270,6 +270,15 @@ export type PhotoStatusEvent = {
270
270
  errorMessage?: string
271
271
  }
272
272
 
273
+ export type CameraStatusEvent = {
274
+ type: "camera_status"
275
+ requestId: string
276
+ state: "warming" | "ready" | "stopped" | "error" | string
277
+ timestamp: number
278
+ errorCode?: string
279
+ errorMessage?: string
280
+ }
281
+
273
282
  export type VideoRecordingStatusEvent = {
274
283
  type: "video_recording_status"
275
284
  requestId?: string
@@ -310,9 +319,7 @@ export type VideoRecordingStoppedStatusEvent = Omit<VideoRecordingStatusEvent, "
310
319
  status: "recording_stopped"
311
320
  }
312
321
 
313
- export type VideoRecordingSuccessStatusEvent =
314
- | VideoRecordingStartedStatusEvent
315
- | VideoRecordingStoppedStatusEvent
322
+ export type VideoRecordingSuccessStatusEvent = VideoRecordingStartedStatusEvent | VideoRecordingStoppedStatusEvent
316
323
 
317
324
  export type MediaUploadSuccessEvent = {
318
325
  type: "media_success"
@@ -396,9 +403,9 @@ export type SettingsAckSetting =
396
403
  | "gallery_mode"
397
404
  | "button_photo"
398
405
  | "button_video_recording"
399
- | "button_camera_led"
400
406
  | "button_max_recording_time"
401
407
  | "camera_fov"
408
+ | "camera_tuning"
402
409
 
403
410
  export type SettingsAckEvent = {
404
411
  type: "settings_ack"
@@ -416,6 +423,10 @@ export type SettingsAckEvent = {
416
423
  fps?: number
417
424
  enabled?: boolean
418
425
  minutes?: number
426
+ /** ANR enabled flag; present when setting === "camera_tuning" */
427
+ anr?: boolean
428
+ /** Stock-gain flag; present when setting === "camera_tuning" */
429
+ gain?: boolean
419
430
  errorCode?: string
420
431
  errorMessage?: string
421
432
  }
@@ -431,8 +442,8 @@ export type RgbLedColor = "red" | "green" | "blue" | "orange" | "white"
431
442
  export type PhotoSize = "low" | "medium" | "high" | "max"
432
443
  export type ButtonPhotoSize = "low" | "medium" | "high" | "max"
433
444
 
434
- export type ButtonPhotoSettings = {
435
- size: ButtonPhotoSize
445
+ export type PhotoCaptureDefaults = {
446
+ size?: PhotoSize
436
447
  mfnr?: boolean
437
448
  zsl?: boolean
438
449
  noiseReduction?: boolean
@@ -448,9 +459,15 @@ export type ButtonPhotoSettings = {
448
459
  }
449
460
  export type PhotoCompression = "none" | "medium" | "heavy"
450
461
 
462
+ export type VideoRecordingDefaults = {
463
+ width: number
464
+ height: number
465
+ fps: number
466
+ }
467
+
451
468
  /**
452
469
  * Optional per-recording video settings for {@link startVideoRecording}. When
453
- * omitted, the glasses fall back to their saved button-video settings. Any
470
+ * omitted, the glasses fall back to their saved video recording defaults. Any
454
471
  * field left undefined is omitted from the BLE command (glasses default applies).
455
472
  */
456
473
  export interface VideoRecordingSettings {
@@ -473,6 +490,7 @@ export const DeviceModels = {
473
490
  Mach1: "Mentra Mach1",
474
491
  Z100: "Vuzix Z100",
475
492
  Frame: "Brilliant Frame",
493
+ Nimo: "NIMO",
476
494
  R1: "Even Realities R1",
477
495
  } as const
478
496
 
@@ -523,8 +541,7 @@ export type MicPreference = "auto" | "phone" | "glasses" | "bluetooth"
523
541
  export type MicMode = "phone" | "glasses" | "bluetoothClassic" | "bluetooth"
524
542
 
525
543
  export type PhotoRequestParams = {
526
- requestId: string
527
- appId: string
544
+ requestId?: string
528
545
  size: PhotoSize
529
546
  webhookUrl: string | null
530
547
  authToken: string | null
@@ -547,6 +564,13 @@ export type PhotoRequestParams = {
547
564
  ispAnalogGain?: string
548
565
  }
549
566
 
567
+ export type WarmUpCameraParams = {
568
+ requestId?: string
569
+ size: PhotoSize
570
+ exposureTimeNs?: number | null
571
+ durationMs?: number
572
+ }
573
+
550
574
  export type StreamVideoConfig = {
551
575
  width?: number
552
576
  height?: number
@@ -565,8 +589,6 @@ export type StreamStartRequest = {
565
589
  type?: "start_stream"
566
590
  streamUrl: string
567
591
  streamId?: string
568
- keepAlive?: boolean
569
- keepAliveIntervalSeconds?: number
570
592
  sound?: boolean
571
593
  video?: StreamVideoConfig
572
594
  audio?: StreamAudioConfig
@@ -722,15 +744,6 @@ export type MtkUpdateCompleteEvent = {
722
744
  timestamp: number
723
745
  }
724
746
 
725
- export type OtaUpdateAvailableEvent = {
726
- type: "ota_update_available"
727
- version_code?: number
728
- version_name?: string
729
- updates?: string[]
730
- total_size?: number
731
- cache_ready?: boolean
732
- }
733
-
734
747
  /** @deprecated Glasses no longer emit ota_progress; use {@link OtaStatusEvent} and status-store mapping. */
735
748
  export type OtaProgressEvent = {
736
749
  type: "ota_progress"
@@ -761,7 +774,7 @@ export type OtaStatusEvent = {
761
774
  error_message?: string
762
775
  }
763
776
 
764
- export type OtaQueryResult = OtaUpdateAvailableEvent | OtaStatusEvent
777
+ export type OtaQueryResult = OtaStatusEvent
765
778
 
766
779
  /** Nex BLE protobuf trace (NexEventUtils); payload matches native Map keys. */
767
780
  export type BleCommandTraceEvent = {
@@ -800,6 +813,7 @@ export type BluetoothSdkModuleEvents = {
800
813
  hotspot_error: (event: HotspotErrorEvent) => void
801
814
  photo_response: (event: PhotoResponseEvent) => void
802
815
  photo_status: (event: PhotoStatusEvent) => void
816
+ camera_status: (event: CameraStatusEvent) => void
803
817
  video_recording_status: (event: VideoRecordingStatusEvent) => void
804
818
  media_success: (event: MediaUploadSuccessEvent) => void
805
819
  media_error: (event: MediaUploadErrorEvent) => void
@@ -823,7 +837,6 @@ export type BluetoothSdkModuleEvents = {
823
837
  stream_status: (event: StreamStatusEvent) => void
824
838
  keep_alive_ack: (event: KeepAliveAckEvent) => void
825
839
  mtk_update_complete: (event: MtkUpdateCompleteEvent) => void
826
- ota_update_available: (event: OtaUpdateAvailableEvent) => void
827
840
  ota_start_ack: (event: OtaStartAckEvent) => void
828
841
  ota_status: (event: OtaStatusEvent) => void
829
842
  version_info: (event: VersionInfoEvent) => void
@@ -877,6 +890,7 @@ export type BluetoothSdkEventMap = {
877
890
  hotspot_error: HotspotErrorEvent
878
891
  photo_response: PhotoResponseEvent
879
892
  photo_status: PhotoStatusEvent
893
+ camera_status: CameraStatusEvent
880
894
  video_recording_status: VideoRecordingStatusEvent
881
895
  media_success: MediaUploadSuccessEvent
882
896
  media_error: MediaUploadErrorEvent
@@ -946,13 +960,27 @@ export interface BluetoothSdkPublicModule {
946
960
 
947
961
  setGalleryModeEnabled(enabled: boolean): Promise<SettingsAckSuccessEvent>
948
962
  setVoiceActivityDetectionEnabled(enabled: boolean): Promise<void>
949
- setButtonPhotoSettings(settings: ButtonPhotoSettings): Promise<SettingsAckSuccessEvent>
950
- setButtonVideoRecordingSettings(width: number, height: number, fps: number): Promise<SettingsAckSuccessEvent>
951
- setButtonCameraLed(enabled: boolean): Promise<SettingsAckSuccessEvent>
952
- setButtonMaxRecordingTime(minutes: number): Promise<SettingsAckSuccessEvent>
963
+ setPhotoCaptureDefaults(settings: PhotoCaptureDefaults): Promise<SettingsAckSuccessEvent>
964
+ setVideoRecordingDefaults(settings: VideoRecordingDefaults): Promise<SettingsAckSuccessEvent>
965
+ setMaxVideoRecordingDuration(minutes: number): Promise<SettingsAckSuccessEvent>
953
966
  setCameraFov(request: CameraFovRequest): Promise<CameraFovResult>
967
+ /**
968
+ * Configure camera HAL tuning (ANR / gain) on Mentra Live glasses.
969
+ *
970
+ * The phone sends a {@code camera_tuning_config} BLE command; the glasses relay it as a
971
+ * {@code camconfig} broadcast to the camera HAL so parameters take effect without a reboot.
972
+ *
973
+ * **Scan-mode convention**: call with `(false, false)` when activating scan mode to disable ANR
974
+ * and pixsmart gain for sharper text/barcode captures. Call with `(true, true)` to restore
975
+ * defaults when exiting scan mode.
976
+ *
977
+ * @param anrOn `true` = ANR enabled (default), `false` = ANR disabled
978
+ * @param gainOn `true` = stock gain params (default), `false` = pixsmart gain-off params
979
+ */
980
+ setCameraTuningConfig(anrOn: boolean, gainOn: boolean): Promise<SettingsAckSuccessEvent>
954
981
  queryGalleryStatus(): Promise<GalleryStatusEvent>
955
982
  requestPhoto(params: PhotoRequestParams): Promise<PhotoSuccessResponseEvent>
983
+ warmUpCamera(params: WarmUpCameraParams): Promise<CameraStatusEvent>
956
984
  startVideoRecording(
957
985
  requestId: string,
958
986
  save: boolean,
@@ -1057,7 +1085,6 @@ export interface OtaUpdateInfo {
1057
1085
  versionName: string
1058
1086
  updates: string[] // ["apk", "mtk", "bes"]
1059
1087
  totalSize: number
1060
- cacheReady?: boolean
1061
1088
  }
1062
1089
 
1063
1090
  export interface OtaProgress {
@@ -1237,7 +1264,6 @@ export type BluetoothSettingsUpdate = Partial<{
1237
1264
  button_video_width: number
1238
1265
  button_video_height: number
1239
1266
  button_video_fps: number
1240
- button_camera_led: boolean
1241
1267
  button_max_recording_time: number
1242
1268
  camera_fov: NativeCameraFovSetting
1243
1269
  should_send_pcm: boolean
package/src/_internal.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * MentraOS-only compatibility entrypoint.
3
3
  *
4
- * Apps should import from `@mentra/bluetooth-sdk`. This file is not a
5
- * package export; MentraOS resolves it through its local
6
- * `@mentra/bluetooth-sdk-internal` alias while the app is migrated onto the
7
- * public SDK surface.
4
+ * Apps should import from `@mentra/bluetooth-sdk`. This is exported as
5
+ * `@mentra/bluetooth-sdk/internal` only for MentraOS packages that still need
6
+ * the compatibility surface while the app is migrated onto the public SDK
7
+ * surface.
8
8
  */
9
9
  export {default} from "./_private/BluetoothSdkModule"
10
10
  export type {BluetoothSdkInternalModule} from "./_private/BluetoothSdkModule"
@@ -4,7 +4,7 @@ import {
4
4
  BluetoothSettingsUpdate,
5
5
  BluetoothSdkModuleEvents,
6
6
  BluetoothStatus,
7
- ButtonPhotoSettings,
7
+ PhotoCaptureDefaults,
8
8
  CalendarEvent,
9
9
  CAMERA_FOV_DEFAULT,
10
10
  CAMERA_FOV_MAX,
@@ -14,6 +14,7 @@ import {
14
14
  CameraFovResult,
15
15
  CameraFovSetting,
16
16
  CameraRoiPosition,
17
+ CameraStatusEvent,
17
18
  ConnectOptions,
18
19
  DashboardMenuItem,
19
20
  Device,
@@ -43,9 +44,11 @@ import {
43
44
  VideoRecordingSettings,
44
45
  VideoRecordingStoppedStatusEvent,
45
46
  VersionInfoResult,
47
+ WarmUpCameraParams,
46
48
  WifiSearchResult,
47
49
  WifiStatusChangeEvent,
48
50
  } from "../BluetoothSdk.types"
51
+ import {warmUpCameraParamsForNative} from "./cameraRequestPayload"
49
52
  import {photoRequestParamsForNative} from "./photoRequestPayload"
50
53
 
51
54
  /**
@@ -121,14 +124,22 @@ declare class BluetoothSdkNativeModule extends NativeModule<BluetoothSdkModuleEv
121
124
  // Gallery Commands
122
125
  setGalleryModeEnabled(enabled: boolean): Promise<SettingsAckSuccessEvent>
123
126
  setVoiceActivityDetectionEnabled(enabled: boolean): Promise<void>
124
- setButtonPhotoSettings(settings: ButtonPhotoSettings): Promise<SettingsAckSuccessEvent>
125
- setButtonPhotoCaptureSettings(settings: ButtonPhotoSettings): Promise<SettingsAckSuccessEvent>
126
- setButtonVideoRecordingSettings(width: number, height: number, fps: number): Promise<SettingsAckSuccessEvent>
127
- setButtonCameraLed(enabled: boolean): Promise<SettingsAckSuccessEvent>
128
- setButtonMaxRecordingTime(minutes: number): Promise<SettingsAckSuccessEvent>
127
+ setPhotoCaptureDefaults(settings: PhotoCaptureDefaults): Promise<SettingsAckSuccessEvent>
128
+ setVideoRecordingDefaults(width: number, height: number, fps: number): Promise<SettingsAckSuccessEvent>
129
+ setMaxVideoRecordingDuration(minutes: number): Promise<SettingsAckSuccessEvent>
129
130
  setCameraFov(request: CameraFovRequest): Promise<CameraFovResult>
131
+ /**
132
+ * Configure camera HAL tuning (ANR / gain) on Mentra Live glasses.
133
+ * Sends a {@code camera_tuning_config} BLE message; the ASG client relays it as a
134
+ * {@code camconfig} broadcast so the HAL picks up new parameters without a reboot.
135
+ *
136
+ * Scan-mode convention: pass `{anr: false, gain: false}` when activating scan mode
137
+ * (disables ANR and pixsmart gain) and `{anr: true, gain: true}` to restore defaults.
138
+ */
139
+ setCameraTuningConfig(anrOn: boolean, gainOn: boolean): Promise<SettingsAckSuccessEvent>
130
140
  queryGalleryStatus(): Promise<GalleryStatusEvent>
131
141
  requestPhoto(params: PhotoRequestParams): Promise<PhotoSuccessResponseEvent>
142
+ warmUpCamera(params: WarmUpCameraParams): Promise<CameraStatusEvent>
132
143
 
133
144
  // OTA Commands
134
145
  setOtaVersionUrl(otaVersionUrl: string): void
@@ -136,8 +147,6 @@ declare class BluetoothSdkNativeModule extends NativeModule<BluetoothSdkModuleEv
136
147
  checkForOtaUpdate(): Promise<boolean>
137
148
  startOtaUpdate(otaVersionUrl?: string | null): Promise<OtaStartAckEvent>
138
149
  sendOtaQueryStatus(): Promise<OtaQueryResult>
139
- /** Re-run glasses-side OTA version check (called after a clock fix invalidates a TLS failure). */
140
- retryOtaVersionCheck(): Promise<OtaQueryResult>
141
150
 
142
151
  // Version Info Commands
143
152
  requestVersionInfo(): Promise<VersionInfoResult>
@@ -568,9 +577,9 @@ NativeBluetoothSdkModule.requestPhoto = function (params: PhotoRequestParams) {
568
577
  return nativeRequestPhoto(photoRequestParamsForNative(params) as unknown as PhotoRequestParams)
569
578
  }
570
579
 
571
- const nativeStartStream = NativeBluetoothSdkModule.startStream.bind(NativeBluetoothSdkModule)
572
- NativeBluetoothSdkModule.startExternallyManagedStream = function (params: StreamStartRequest) {
573
- return nativeStartStream({...params, keepAliveMode: "external"} as StreamStartRequest)
580
+ const nativeWarmUpCamera = NativeBluetoothSdkModule.warmUpCamera.bind(NativeBluetoothSdkModule)
581
+ NativeBluetoothSdkModule.warmUpCamera = function (params: WarmUpCameraParams) {
582
+ return nativeWarmUpCamera(warmUpCameraParamsForNative(params) as unknown as WarmUpCameraParams)
574
583
  }
575
584
 
576
585
  export default NativeBluetoothSdkModule
@@ -0,0 +1,29 @@
1
+ import type {WarmUpCameraParams} from "../BluetoothSdk.types"
2
+ import {normalizePhotoSizeTier} from "./photoRequestPayload"
3
+
4
+ function nonBlankString(value?: string | null): string | undefined {
5
+ const trimmed = value?.trim()
6
+ return trimmed && trimmed.length > 0 ? trimmed : undefined
7
+ }
8
+
9
+ /** Expo Android bridge rejects null values in Map<String, Any> — omit optional nullish fields. */
10
+ export function warmUpCameraParamsForNative(
11
+ params: WarmUpCameraParams,
12
+ ): Record<string, string | number> {
13
+ const payload: Record<string, string | number> = {
14
+ size: normalizePhotoSizeTier(params.size),
15
+ }
16
+ const requestId = nonBlankString(params.requestId)
17
+ if (requestId != null) {
18
+ payload.requestId = requestId
19
+ }
20
+ const exposureTimeNs = params.exposureTimeNs
21
+ if (exposureTimeNs != null && Number.isFinite(exposureTimeNs) && exposureTimeNs > 0) {
22
+ payload.exposureTimeNs = exposureTimeNs
23
+ }
24
+ const durationMs = params.durationMs
25
+ if (durationMs != null && Number.isFinite(durationMs) && durationMs > 0) {
26
+ payload.durationMs = Math.round(durationMs)
27
+ }
28
+ return payload
29
+ }
@@ -19,19 +19,28 @@ export function normalizePhotoSizeTier(size: string | undefined): PhotoSize {
19
19
  }
20
20
  }
21
21
 
22
+ function nonBlankString(value?: string | null): string | undefined {
23
+ const trimmed = value?.trim()
24
+ return trimmed && trimmed.length > 0 ? trimmed : undefined
25
+ }
26
+
22
27
  /** Expo Android bridge rejects null values in Map<String, Any> — omit optional nullish fields. */
23
28
  export function photoRequestParamsForNative(
24
29
  params: PhotoRequestParams,
25
30
  ): Record<string, string | number | boolean> {
26
31
  const payload: Record<string, string | number | boolean> = {
27
- requestId: params.requestId,
28
- appId: params.appId,
29
32
  size: normalizePhotoSizeTier(params.size),
30
33
  webhookUrl: params.webhookUrl ?? "",
31
34
  compress: params.compress,
32
- flash: true,
33
35
  sound: params.sound,
34
36
  }
37
+ const requestId = nonBlankString(params.requestId)
38
+ if (requestId != null) {
39
+ payload.requestId = requestId
40
+ }
41
+ if (params.save != null) {
42
+ payload.save = params.save
43
+ }
35
44
  if (params.authToken != null && params.authToken.length > 0) {
36
45
  payload.authToken = params.authToken
37
46
  }