@mentra/bluetooth-sdk 0.1.14 → 0.1.17

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 (67) hide show
  1. package/README.md +4 -6
  2. package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +22 -9
  3. package/android/src/main/java/com/mentra/bluetoothsdk/Bridge.kt +5 -20
  4. package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +358 -261
  5. package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +3 -3
  6. package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +133 -52
  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 +22 -6
  9. package/android/src/main/java/com/mentra/bluetoothsdk/events/BluetoothEvents.kt +1 -22
  10. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G1.kt +50 -3
  11. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G2.kt +1184 -795
  12. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.kt +422 -78
  13. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraNex.kt +3 -4
  14. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Nimo.kt +2532 -0
  15. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/SGCManager.kt +15 -1
  16. package/android/src/main/java/com/mentra/bluetoothsdk/types/DeviceModels.kt +5 -3
  17. package/android/src/main/java/com/mentra/bluetoothsdk/utils/Constants.kt +2 -1
  18. package/android/src/test/java/com/mentra/bluetoothsdk/camera/PhotoRequestTest.kt +60 -1
  19. package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/NimoProtocolTest.kt +333 -0
  20. package/build/BluetoothSdk.types.d.ts +39 -14
  21. package/build/BluetoothSdk.types.d.ts.map +1 -1
  22. package/build/BluetoothSdk.types.js +1 -0
  23. package/build/BluetoothSdk.types.js.map +1 -1
  24. package/build/_internal.d.ts +4 -4
  25. package/build/_internal.js +4 -4
  26. package/build/_internal.js.map +1 -1
  27. package/build/_private/BluetoothSdkModule.d.ts +11 -3
  28. package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
  29. package/build/_private/BluetoothSdkModule.js +5 -0
  30. package/build/_private/BluetoothSdkModule.js.map +1 -1
  31. package/build/_private/cameraRequestPayload.d.ts +4 -0
  32. package/build/_private/cameraRequestPayload.d.ts.map +1 -0
  33. package/build/_private/cameraRequestPayload.js +25 -0
  34. package/build/_private/cameraRequestPayload.js.map +1 -0
  35. package/build/_private/photoRequestPayload.d.ts.map +1 -1
  36. package/build/_private/photoRequestPayload.js +8 -2
  37. package/build/_private/photoRequestPayload.js.map +1 -1
  38. package/build/index.d.ts +1 -1
  39. package/build/index.d.ts.map +1 -1
  40. package/build/index.js +72 -54
  41. package/build/index.js.map +1 -1
  42. package/ios/BluetoothSdkModule.swift +40 -12
  43. package/ios/Source/BluetoothSdkDefaults.swift +2 -30
  44. package/ios/Source/Bridge.swift +4 -16
  45. package/ios/Source/DeviceManager.swift +72 -12
  46. package/ios/Source/MentraBluetoothSDK.swift +120 -50
  47. package/ios/Source/OtaManifest.swift +13 -13
  48. package/ios/Source/camera/CameraModels.swift +75 -9
  49. package/ios/Source/errors/{BluetoothError.swift → BluetoothSdkError.swift} +1 -1
  50. package/ios/Source/events/BluetoothEvents.swift +5 -27
  51. package/ios/Source/internal/BluetoothAvailability.swift +5 -5
  52. package/ios/Source/sgcs/G1.swift +51 -1
  53. package/ios/Source/sgcs/G2.swift +643 -203
  54. package/ios/Source/sgcs/MentraLive.swift +53 -39
  55. package/ios/Source/sgcs/Nimo.swift +1928 -0
  56. package/ios/Source/sgcs/SGCManager.swift +12 -5
  57. package/ios/Source/stt/STTTools.swift +1 -1
  58. package/ios/Source/types/DeviceModels.swift +5 -0
  59. package/ios/Source/utils/Constants.swift +2 -0
  60. package/package.json +9 -1
  61. package/scripts/inject-ios-sdk-version.mjs +65 -0
  62. package/src/BluetoothSdk.types.ts +42 -17
  63. package/src/_internal.ts +4 -4
  64. package/src/_private/BluetoothSdkModule.ts +18 -2
  65. package/src/_private/cameraRequestPayload.ts +29 -0
  66. package/src/_private/photoRequestPayload.ts +9 -2
  67. package/src/index.ts +76 -58
@@ -56,6 +56,12 @@ protocol SGCManager {
56
56
  /// Display a bitmap. Optional `x`/`y`/`width`/`height` position and size the target
57
57
  /// container (used by G2; other SGCs ignore positioning and render the bitmap as before).
58
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
59
65
  func showDashboard()
60
66
  func setDashboardPosition(_ height: Int, _ depth: Int)
61
67
  /// Default implementation sends both via [setDashboardPosition]; Nex overrides to one protobuf.
@@ -118,7 +124,6 @@ protocol SGCManager {
118
124
  func sendOtaStart(otaVersionUrl: String?)
119
125
  func sendOtaQueryStatus()
120
126
  func sendSetSystemTime(_ timestampMs: Int64)
121
- func sendOtaRetryVersionCheck()
122
127
 
123
128
  // MARK: - User Context (for crash reporting)
124
129
 
@@ -145,6 +150,12 @@ protocol SGCManager {
145
150
  /// doesn't seem to work for concurrency reasons :(
146
151
  /// we can make read-only getters for convienence though:
147
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
+
148
159
  // MARK: - Video recording (default: ignore custom settings, use saved defaults)
149
160
 
150
161
  func startVideoRecording(
@@ -201,10 +212,6 @@ extension SGCManager {
201
212
  Bridge.log("SGC: sendSetSystemTime not supported")
202
213
  }
203
214
 
204
- func sendOtaRetryVersionCheck() {
205
- Bridge.log("SGC: sendOtaRetryVersionCheck not supported")
206
- }
207
-
208
215
  // MARK: - Default DeviceStore-backed property implementations
209
216
 
210
217
  var fullyBooted: Bool {
@@ -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.14",
3
+ "version": "0.1.17",
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",
@@ -54,6 +59,7 @@
54
59
  "!ios/Pods/**",
55
60
  "plugin/build",
56
61
  "README.md",
62
+ "scripts",
57
63
  "src",
58
64
  "!src/__tests__"
59
65
  ],
@@ -63,6 +69,8 @@
63
69
  "clean": "expo-module clean",
64
70
  "test": "expo-module test",
65
71
  "prepare": "expo-module prepare",
72
+ "prepack": "node scripts/inject-ios-sdk-version.mjs",
73
+ "postpack": "node scripts/inject-ios-sdk-version.mjs --restore",
66
74
  "prepublishOnly": "expo-module prepublishOnly",
67
75
  "lint": "expo-module lint",
68
76
  "expo-module": "expo-module",
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from "node:fs/promises"
4
+ import path from "node:path"
5
+ import { fileURLToPath } from "node:url"
6
+
7
+ const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..")
8
+ const packageJsonPath = path.join(packageRoot, "package.json")
9
+ const sourcePath = path.join(packageRoot, "ios/Source/BluetoothSdkDefaults.swift")
10
+ const backupPath = path.join(packageRoot, ".bluetooth-sdk-version-prepack-backup")
11
+ const restore = process.argv.includes("--restore")
12
+
13
+ const packageJson = JSON.parse(await fs.readFile(packageJsonPath, "utf8"))
14
+ const version = packageJson.version
15
+
16
+ if (typeof version !== "string" || version.trim() === "") {
17
+ throw new Error("mobile/modules/bluetooth-sdk/package.json is missing a version")
18
+ }
19
+
20
+ const placeholder = 'private static let swiftPackageSdkVersion = "__MENTRA_BLUETOOTH_SDK_VERSION__"'
21
+ const injectedVersion = `private static let swiftPackageSdkVersion = "${version}"`
22
+
23
+ async function pathExists(filePath) {
24
+ try {
25
+ await fs.access(filePath)
26
+ return true
27
+ } catch {
28
+ return false
29
+ }
30
+ }
31
+
32
+ if (restore) {
33
+ if (!(await pathExists(backupPath))) {
34
+ console.log("No iOS SDK version prepack backup found; nothing to restore.")
35
+ process.exit(0)
36
+ }
37
+
38
+ await fs.writeFile(sourcePath, await fs.readFile(backupPath, "utf8"))
39
+ await fs.rm(backupPath)
40
+ console.log("Restored placeholder iOS SDK version after packing.")
41
+ process.exit(0)
42
+ }
43
+
44
+ if (await pathExists(backupPath)) {
45
+ throw new Error("Found an existing iOS SDK version prepack backup. Run `npm run postpack` before packing again.")
46
+ }
47
+
48
+ const originalSource = await fs.readFile(sourcePath, "utf8")
49
+ let packedSource
50
+
51
+ if (originalSource.includes(placeholder)) {
52
+ packedSource = originalSource.replace(placeholder, injectedVersion)
53
+ } else if (originalSource.includes(injectedVersion)) {
54
+ packedSource = originalSource
55
+ } else {
56
+ throw new Error("BluetoothSdkDefaults.swift does not contain the expected SDK version placeholder.")
57
+ }
58
+
59
+ await fs.writeFile(backupPath, originalSource)
60
+
61
+ if (packedSource !== originalSource) {
62
+ await fs.writeFile(sourcePath, packedSource)
63
+ }
64
+
65
+ console.log(`Injected iOS SDK version ${version} for npm packaging.`)
@@ -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"
@@ -398,6 +405,7 @@ export type SettingsAckSetting =
398
405
  | "button_video_recording"
399
406
  | "button_max_recording_time"
400
407
  | "camera_fov"
408
+ | "camera_tuning"
401
409
 
402
410
  export type SettingsAckEvent = {
403
411
  type: "settings_ack"
@@ -415,6 +423,10 @@ export type SettingsAckEvent = {
415
423
  fps?: number
416
424
  enabled?: boolean
417
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
418
430
  errorCode?: string
419
431
  errorMessage?: string
420
432
  }
@@ -478,6 +490,7 @@ export const DeviceModels = {
478
490
  Mach1: "Mentra Mach1",
479
491
  Z100: "Vuzix Z100",
480
492
  Frame: "Brilliant Frame",
493
+ Nimo: "NIMO",
481
494
  R1: "Even Realities R1",
482
495
  } as const
483
496
 
@@ -528,8 +541,7 @@ export type MicPreference = "auto" | "phone" | "glasses" | "bluetooth"
528
541
  export type MicMode = "phone" | "glasses" | "bluetoothClassic" | "bluetooth"
529
542
 
530
543
  export type PhotoRequestParams = {
531
- requestId: string
532
- appId: string
544
+ requestId?: string
533
545
  size: PhotoSize
534
546
  webhookUrl: string | null
535
547
  authToken: string | null
@@ -552,6 +564,13 @@ export type PhotoRequestParams = {
552
564
  ispAnalogGain?: string
553
565
  }
554
566
 
567
+ export type WarmUpCameraParams = {
568
+ requestId?: string
569
+ size: PhotoSize
570
+ exposureTimeNs?: number | null
571
+ durationMs?: number
572
+ }
573
+
555
574
  export type StreamVideoConfig = {
556
575
  width?: number
557
576
  height?: number
@@ -725,15 +744,6 @@ export type MtkUpdateCompleteEvent = {
725
744
  timestamp: number
726
745
  }
727
746
 
728
- export type OtaUpdateAvailableEvent = {
729
- type: "ota_update_available"
730
- version_code?: number
731
- version_name?: string
732
- updates?: string[]
733
- total_size?: number
734
- cache_ready?: boolean
735
- }
736
-
737
747
  /** @deprecated Glasses no longer emit ota_progress; use {@link OtaStatusEvent} and status-store mapping. */
738
748
  export type OtaProgressEvent = {
739
749
  type: "ota_progress"
@@ -764,7 +774,7 @@ export type OtaStatusEvent = {
764
774
  error_message?: string
765
775
  }
766
776
 
767
- export type OtaQueryResult = OtaUpdateAvailableEvent | OtaStatusEvent
777
+ export type OtaQueryResult = OtaStatusEvent
768
778
 
769
779
  /** Nex BLE protobuf trace (NexEventUtils); payload matches native Map keys. */
770
780
  export type BleCommandTraceEvent = {
@@ -803,6 +813,7 @@ export type BluetoothSdkModuleEvents = {
803
813
  hotspot_error: (event: HotspotErrorEvent) => void
804
814
  photo_response: (event: PhotoResponseEvent) => void
805
815
  photo_status: (event: PhotoStatusEvent) => void
816
+ camera_status: (event: CameraStatusEvent) => void
806
817
  video_recording_status: (event: VideoRecordingStatusEvent) => void
807
818
  media_success: (event: MediaUploadSuccessEvent) => void
808
819
  media_error: (event: MediaUploadErrorEvent) => void
@@ -826,7 +837,6 @@ export type BluetoothSdkModuleEvents = {
826
837
  stream_status: (event: StreamStatusEvent) => void
827
838
  keep_alive_ack: (event: KeepAliveAckEvent) => void
828
839
  mtk_update_complete: (event: MtkUpdateCompleteEvent) => void
829
- ota_update_available: (event: OtaUpdateAvailableEvent) => void
830
840
  ota_start_ack: (event: OtaStartAckEvent) => void
831
841
  ota_status: (event: OtaStatusEvent) => void
832
842
  version_info: (event: VersionInfoEvent) => void
@@ -880,6 +890,7 @@ export type BluetoothSdkEventMap = {
880
890
  hotspot_error: HotspotErrorEvent
881
891
  photo_response: PhotoResponseEvent
882
892
  photo_status: PhotoStatusEvent
893
+ camera_status: CameraStatusEvent
883
894
  video_recording_status: VideoRecordingStatusEvent
884
895
  media_success: MediaUploadSuccessEvent
885
896
  media_error: MediaUploadErrorEvent
@@ -953,8 +964,23 @@ export interface BluetoothSdkPublicModule {
953
964
  setVideoRecordingDefaults(settings: VideoRecordingDefaults): Promise<SettingsAckSuccessEvent>
954
965
  setMaxVideoRecordingDuration(minutes: number): Promise<SettingsAckSuccessEvent>
955
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>
956
981
  queryGalleryStatus(): Promise<GalleryStatusEvent>
957
982
  requestPhoto(params: PhotoRequestParams): Promise<PhotoSuccessResponseEvent>
983
+ warmUpCamera(params: WarmUpCameraParams): Promise<CameraStatusEvent>
958
984
  startVideoRecording(
959
985
  requestId: string,
960
986
  save: boolean,
@@ -1059,7 +1085,6 @@ export interface OtaUpdateInfo {
1059
1085
  versionName: string
1060
1086
  updates: string[] // ["apk", "mtk", "bes"]
1061
1087
  totalSize: number
1062
- cacheReady?: boolean
1063
1088
  }
1064
1089
 
1065
1090
  export interface OtaProgress {
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"
@@ -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
  /**
@@ -125,8 +128,18 @@ declare class BluetoothSdkNativeModule extends NativeModule<BluetoothSdkModuleEv
125
128
  setVideoRecordingDefaults(width: number, height: number, fps: number): Promise<SettingsAckSuccessEvent>
126
129
  setMaxVideoRecordingDuration(minutes: number): Promise<SettingsAckSuccessEvent>
127
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>
128
140
  queryGalleryStatus(): Promise<GalleryStatusEvent>
129
141
  requestPhoto(params: PhotoRequestParams): Promise<PhotoSuccessResponseEvent>
142
+ warmUpCamera(params: WarmUpCameraParams): Promise<CameraStatusEvent>
130
143
 
131
144
  // OTA Commands
132
145
  setOtaVersionUrl(otaVersionUrl: string): void
@@ -134,8 +147,6 @@ declare class BluetoothSdkNativeModule extends NativeModule<BluetoothSdkModuleEv
134
147
  checkForOtaUpdate(): Promise<boolean>
135
148
  startOtaUpdate(otaVersionUrl?: string | null): Promise<OtaStartAckEvent>
136
149
  sendOtaQueryStatus(): Promise<OtaQueryResult>
137
- /** Re-run glasses-side OTA version check (called after a clock fix invalidates a TLS failure). */
138
- retryOtaVersionCheck(): Promise<OtaQueryResult>
139
150
 
140
151
  // Version Info Commands
141
152
  requestVersionInfo(): Promise<VersionInfoResult>
@@ -566,5 +577,10 @@ NativeBluetoothSdkModule.requestPhoto = function (params: PhotoRequestParams) {
566
577
  return nativeRequestPhoto(photoRequestParamsForNative(params) as unknown as PhotoRequestParams)
567
578
  }
568
579
 
580
+ const nativeWarmUpCamera = NativeBluetoothSdkModule.warmUpCamera.bind(NativeBluetoothSdkModule)
581
+ NativeBluetoothSdkModule.warmUpCamera = function (params: WarmUpCameraParams) {
582
+ return nativeWarmUpCamera(warmUpCameraParamsForNative(params) as unknown as WarmUpCameraParams)
583
+ }
584
+
569
585
  export default NativeBluetoothSdkModule
570
586
  export const BluetoothSdk = NativeBluetoothSdkModule as BluetoothSdkInternalModule
@@ -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,18 +19,25 @@ 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
35
  sound: params.sound,
33
36
  }
37
+ const requestId = nonBlankString(params.requestId)
38
+ if (requestId != null) {
39
+ payload.requestId = requestId
40
+ }
34
41
  if (params.save != null) {
35
42
  payload.save = params.save
36
43
  }
package/src/index.ts CHANGED
@@ -25,6 +25,7 @@ const PUBLIC_EVENT_NAMES = new Set<BluetoothSdkEventName>([
25
25
  "hotspot_error",
26
26
  "photo_response",
27
27
  "photo_status",
28
+ "camera_status",
28
29
  "video_recording_status",
29
30
  "media_success",
30
31
  "media_error",
@@ -58,65 +59,84 @@ const startOtaUpdate: BluetoothSdkPublicModule["startOtaUpdate"] = () => {
58
59
  return PrivateBluetoothSdkModule.startOtaUpdate()
59
60
  }
60
61
 
62
+ const bindPublicMethod = <K extends keyof BluetoothSdkPublicModule>(name: K): BluetoothSdkPublicModule[K] => {
63
+ const method = (PrivateBluetoothSdkModule as unknown as Record<string, unknown>)[name]
64
+ if (typeof method === "function") {
65
+ return method.bind(PrivateBluetoothSdkModule) as BluetoothSdkPublicModule[K]
66
+ }
67
+
68
+ return (async () => {
69
+ throw new Error(`BluetoothSdk.${String(name)} is not available in this native build. Rebuild the app.`)
70
+ }) as unknown as BluetoothSdkPublicModule[K]
71
+ }
72
+
61
73
  export const BluetoothSdk: BluetoothSdkPublicModule = Object.freeze({
62
74
  addListener,
63
- getDefaultDevice: PrivateBluetoothSdkModule.getDefaultDevice.bind(PrivateBluetoothSdkModule),
64
- setDefaultDevice: PrivateBluetoothSdkModule.setDefaultDevice.bind(PrivateBluetoothSdkModule),
65
- clearDefaultDevice: PrivateBluetoothSdkModule.clearDefaultDevice.bind(PrivateBluetoothSdkModule),
66
- startScan: PrivateBluetoothSdkModule.startScan.bind(PrivateBluetoothSdkModule),
67
- stopScan: PrivateBluetoothSdkModule.stopScan.bind(PrivateBluetoothSdkModule),
68
- scan: PrivateBluetoothSdkModule.scan.bind(PrivateBluetoothSdkModule) as BluetoothSdkPublicModule["scan"],
69
- connect: PrivateBluetoothSdkModule.connect.bind(PrivateBluetoothSdkModule),
70
- connectDefault: PrivateBluetoothSdkModule.connectDefault.bind(PrivateBluetoothSdkModule),
71
- cancelConnectionAttempt: PrivateBluetoothSdkModule.cancelConnectionAttempt.bind(PrivateBluetoothSdkModule),
72
- disconnect: PrivateBluetoothSdkModule.disconnect.bind(PrivateBluetoothSdkModule),
73
- forget: PrivateBluetoothSdkModule.forget.bind(PrivateBluetoothSdkModule),
74
- displayText: PrivateBluetoothSdkModule.displayText.bind(PrivateBluetoothSdkModule),
75
- clearDisplay: PrivateBluetoothSdkModule.clearDisplay.bind(PrivateBluetoothSdkModule),
76
- showDashboard: PrivateBluetoothSdkModule.showDashboard.bind(PrivateBluetoothSdkModule),
77
- setDashboardPosition: PrivateBluetoothSdkModule.setDashboardPosition.bind(PrivateBluetoothSdkModule),
78
- setHeadUpAngle: PrivateBluetoothSdkModule.setHeadUpAngle.bind(PrivateBluetoothSdkModule),
79
- setImuEnabled: PrivateBluetoothSdkModule.setImuEnabled.bind(PrivateBluetoothSdkModule),
80
- setScreenDisabled: PrivateBluetoothSdkModule.setScreenDisabled.bind(PrivateBluetoothSdkModule),
81
- requestWifiScan: PrivateBluetoothSdkModule.requestWifiScan.bind(PrivateBluetoothSdkModule),
82
- sendWifiCredentials: PrivateBluetoothSdkModule.sendWifiCredentials.bind(PrivateBluetoothSdkModule),
83
- forgetWifiNetwork: PrivateBluetoothSdkModule.forgetWifiNetwork.bind(PrivateBluetoothSdkModule),
84
- setHotspotState: PrivateBluetoothSdkModule.setHotspotState.bind(PrivateBluetoothSdkModule),
85
- setGalleryModeEnabled: PrivateBluetoothSdkModule.setGalleryModeEnabled.bind(PrivateBluetoothSdkModule),
86
- setVoiceActivityDetectionEnabled:
87
- PrivateBluetoothSdkModule.setVoiceActivityDetectionEnabled.bind(PrivateBluetoothSdkModule),
88
- setPhotoCaptureDefaults: PrivateBluetoothSdkModule.setPhotoCaptureDefaults.bind(PrivateBluetoothSdkModule),
89
- setVideoRecordingDefaults: ({width, height, fps}: VideoRecordingDefaults) =>
90
- PrivateBluetoothSdkModule.setVideoRecordingDefaults(width, height, fps),
91
- setMaxVideoRecordingDuration: PrivateBluetoothSdkModule.setMaxVideoRecordingDuration.bind(PrivateBluetoothSdkModule),
92
- setCameraFov: PrivateBluetoothSdkModule.setCameraFov.bind(PrivateBluetoothSdkModule),
93
- queryGalleryStatus: PrivateBluetoothSdkModule.queryGalleryStatus.bind(PrivateBluetoothSdkModule),
94
- requestPhoto: PrivateBluetoothSdkModule.requestPhoto.bind(PrivateBluetoothSdkModule),
95
- startVideoRecording: PrivateBluetoothSdkModule.startVideoRecording.bind(PrivateBluetoothSdkModule),
96
- stopVideoRecording: PrivateBluetoothSdkModule.stopVideoRecording.bind(PrivateBluetoothSdkModule),
97
- startStream: PrivateBluetoothSdkModule.startStream.bind(PrivateBluetoothSdkModule),
98
- stopStream: PrivateBluetoothSdkModule.stopStream.bind(PrivateBluetoothSdkModule),
99
- setMicState: PrivateBluetoothSdkModule.setMicState.bind(PrivateBluetoothSdkModule),
100
- setPreferredMic: PrivateBluetoothSdkModule.setPreferredMic.bind(PrivateBluetoothSdkModule),
101
- setOwnAppAudioPlaying: PrivateBluetoothSdkModule.setOwnAppAudioPlaying.bind(PrivateBluetoothSdkModule),
102
- getGlassesMediaVolume: PrivateBluetoothSdkModule.getGlassesMediaVolume.bind(PrivateBluetoothSdkModule),
103
- setGlassesMediaVolume: PrivateBluetoothSdkModule.setGlassesMediaVolume.bind(PrivateBluetoothSdkModule),
104
- rgbLedControl: PrivateBluetoothSdkModule.rgbLedControl.bind(PrivateBluetoothSdkModule),
105
- requestVersionInfo: PrivateBluetoothSdkModule.requestVersionInfo.bind(PrivateBluetoothSdkModule),
106
- checkForOtaUpdate: PrivateBluetoothSdkModule.checkForOtaUpdate.bind(PrivateBluetoothSdkModule),
75
+ getDefaultDevice: bindPublicMethod("getDefaultDevice"),
76
+ setDefaultDevice: bindPublicMethod("setDefaultDevice"),
77
+ clearDefaultDevice: bindPublicMethod("clearDefaultDevice"),
78
+ startScan: bindPublicMethod("startScan"),
79
+ stopScan: bindPublicMethod("stopScan"),
80
+ scan: bindPublicMethod("scan") as BluetoothSdkPublicModule["scan"],
81
+ connect: bindPublicMethod("connect"),
82
+ connectDefault: bindPublicMethod("connectDefault"),
83
+ cancelConnectionAttempt: bindPublicMethod("cancelConnectionAttempt"),
84
+ disconnect: bindPublicMethod("disconnect"),
85
+ forget: bindPublicMethod("forget"),
86
+ displayText: bindPublicMethod("displayText"),
87
+ clearDisplay: bindPublicMethod("clearDisplay"),
88
+ showDashboard: bindPublicMethod("showDashboard"),
89
+ setDashboardPosition: bindPublicMethod("setDashboardPosition"),
90
+ setHeadUpAngle: bindPublicMethod("setHeadUpAngle"),
91
+ setImuEnabled: bindPublicMethod("setImuEnabled"),
92
+ setScreenDisabled: bindPublicMethod("setScreenDisabled"),
93
+ requestWifiScan: bindPublicMethod("requestWifiScan"),
94
+ sendWifiCredentials: bindPublicMethod("sendWifiCredentials"),
95
+ forgetWifiNetwork: bindPublicMethod("forgetWifiNetwork"),
96
+ setHotspotState: bindPublicMethod("setHotspotState"),
97
+ setGalleryModeEnabled: bindPublicMethod("setGalleryModeEnabled"),
98
+ setVoiceActivityDetectionEnabled: bindPublicMethod("setVoiceActivityDetectionEnabled"),
99
+ setPhotoCaptureDefaults: bindPublicMethod("setPhotoCaptureDefaults"),
100
+ setVideoRecordingDefaults: ({width, height, fps}: VideoRecordingDefaults) => {
101
+ const method = (PrivateBluetoothSdkModule as unknown as Record<string, unknown>).setVideoRecordingDefaults
102
+ if (typeof method !== "function") {
103
+ return Promise.reject(
104
+ new Error("BluetoothSdk.setVideoRecordingDefaults is not available in this native build. Rebuild the app."),
105
+ )
106
+ }
107
+ return PrivateBluetoothSdkModule.setVideoRecordingDefaults(width, height, fps)
108
+ },
109
+ setMaxVideoRecordingDuration: bindPublicMethod("setMaxVideoRecordingDuration"),
110
+ setCameraFov: bindPublicMethod("setCameraFov"),
111
+ setCameraTuningConfig: bindPublicMethod("setCameraTuningConfig"),
112
+ queryGalleryStatus: bindPublicMethod("queryGalleryStatus"),
113
+ requestPhoto: bindPublicMethod("requestPhoto"),
114
+ warmUpCamera: bindPublicMethod("warmUpCamera"),
115
+ startVideoRecording: bindPublicMethod("startVideoRecording"),
116
+ stopVideoRecording: bindPublicMethod("stopVideoRecording"),
117
+ startStream: bindPublicMethod("startStream"),
118
+ stopStream: bindPublicMethod("stopStream"),
119
+ setMicState: bindPublicMethod("setMicState"),
120
+ setPreferredMic: bindPublicMethod("setPreferredMic"),
121
+ setOwnAppAudioPlaying: bindPublicMethod("setOwnAppAudioPlaying"),
122
+ getGlassesMediaVolume: bindPublicMethod("getGlassesMediaVolume"),
123
+ setGlassesMediaVolume: bindPublicMethod("setGlassesMediaVolume"),
124
+ rgbLedControl: bindPublicMethod("rgbLedControl"),
125
+ requestVersionInfo: bindPublicMethod("requestVersionInfo"),
126
+ checkForOtaUpdate: bindPublicMethod("checkForOtaUpdate"),
107
127
  startOtaUpdate,
108
- setSttModelDetails: PrivateBluetoothSdkModule.setSttModelDetails.bind(PrivateBluetoothSdkModule),
109
- getSttModelPath: PrivateBluetoothSdkModule.getSttModelPath.bind(PrivateBluetoothSdkModule),
110
- checkSttModelAvailable: PrivateBluetoothSdkModule.checkSttModelAvailable.bind(PrivateBluetoothSdkModule),
111
- validateSttModel: PrivateBluetoothSdkModule.validateSttModel.bind(PrivateBluetoothSdkModule),
112
- extractTarBz2: PrivateBluetoothSdkModule.extractTarBz2.bind(PrivateBluetoothSdkModule),
113
- restartTranscriber: PrivateBluetoothSdkModule.restartTranscriber.bind(PrivateBluetoothSdkModule),
114
- setTtsModelDetails: PrivateBluetoothSdkModule.setTtsModelDetails.bind(PrivateBluetoothSdkModule),
115
- getTtsModelPath: PrivateBluetoothSdkModule.getTtsModelPath.bind(PrivateBluetoothSdkModule),
116
- getTtsModelLanguage: PrivateBluetoothSdkModule.getTtsModelLanguage.bind(PrivateBluetoothSdkModule),
117
- checkTtsModelAvailable: PrivateBluetoothSdkModule.checkTtsModelAvailable.bind(PrivateBluetoothSdkModule),
118
- validateTtsModel: PrivateBluetoothSdkModule.validateTtsModel.bind(PrivateBluetoothSdkModule),
119
- generateTtsAudio: PrivateBluetoothSdkModule.generateTtsAudio.bind(PrivateBluetoothSdkModule),
128
+ setSttModelDetails: bindPublicMethod("setSttModelDetails"),
129
+ getSttModelPath: bindPublicMethod("getSttModelPath"),
130
+ checkSttModelAvailable: bindPublicMethod("checkSttModelAvailable"),
131
+ validateSttModel: bindPublicMethod("validateSttModel"),
132
+ extractTarBz2: bindPublicMethod("extractTarBz2"),
133
+ restartTranscriber: bindPublicMethod("restartTranscriber"),
134
+ setTtsModelDetails: bindPublicMethod("setTtsModelDetails"),
135
+ getTtsModelPath: bindPublicMethod("getTtsModelPath"),
136
+ getTtsModelLanguage: bindPublicMethod("getTtsModelLanguage"),
137
+ checkTtsModelAvailable: bindPublicMethod("checkTtsModelAvailable"),
138
+ validateTtsModel: bindPublicMethod("validateTtsModel"),
139
+ generateTtsAudio: bindPublicMethod("generateTtsAudio"),
120
140
  })
121
141
 
122
142
  export default BluetoothSdk
@@ -178,8 +198,6 @@ export type {
178
198
  OtaStatus,
179
199
  OtaStatusEvent,
180
200
  OtaQueryResult,
181
- OtaUpdateAvailableEvent,
182
- OtaUpdateInfo,
183
201
  PairFailureEvent,
184
202
  PhotoCaptureMetadata,
185
203
  PhotoResolvedConfig,