@mentra/bluetooth-sdk 0.1.20 → 0.1.21-beta.1

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 (114) hide show
  1. package/README.md +27 -12
  2. package/android/build.gradle +3 -0
  3. package/android/src/main/AndroidManifest.xml +3 -1
  4. package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkAnalytics.kt +34 -0
  5. package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkDefaults.kt +1 -0
  6. package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +169 -26
  7. package/android/src/main/java/com/mentra/bluetoothsdk/Bridge.kt +72 -16
  8. package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +127 -8
  9. package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +4 -0
  10. package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +400 -99
  11. package/android/src/main/java/com/mentra/bluetoothsdk/MentraLocalNetworkModule.kt +333 -0
  12. package/android/src/main/java/com/mentra/bluetoothsdk/OtaManifest.kt +43 -3
  13. package/android/src/main/java/com/mentra/bluetoothsdk/camera/CameraModels.kt +18 -2
  14. package/android/src/main/java/com/mentra/bluetoothsdk/debug/BleTraceLogger.kt +1 -1
  15. package/android/src/main/java/com/mentra/bluetoothsdk/services/Foreground.kt +53 -0
  16. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Ar99.java +2653 -0
  17. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G1.kt +1 -1
  18. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G2.kt +16 -9
  19. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Mach1.kt +1 -1
  20. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.kt +342 -42
  21. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraNex.kt +1 -1
  22. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Nimo.kt +1 -1
  23. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/SGCManager.kt +7 -2
  24. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Simulated.kt +1 -1
  25. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/ar99/ota/Ar99OtaManager.java +566 -0
  26. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/ar99/ota/OtaByteUtils.java +18 -0
  27. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/ar99/ota/OtaCommandConstants.java +35 -0
  28. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/ar99/ota/OtaCrc32Util.java +13 -0
  29. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/ar99/ota/OtaGattTransport.java +11 -0
  30. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/ar99/ota/OtaProtocol.java +92 -0
  31. package/android/src/main/java/com/mentra/bluetoothsdk/status/WifiHotspotStatus.kt +19 -3
  32. package/android/src/main/java/com/mentra/bluetoothsdk/streaming/StreamModels.kt +52 -2
  33. package/android/src/main/java/com/mentra/bluetoothsdk/tts/TTSTools.kt +115 -47
  34. package/android/src/main/java/com/mentra/bluetoothsdk/types/DeviceModels.kt +7 -0
  35. package/android/src/main/java/com/mentra/bluetoothsdk/utils/BleJsonCompact.java +8 -0
  36. package/android/src/main/java/com/mentra/bluetoothsdk/utils/Constants.kt +2 -1
  37. package/android/src/main/java/com/mentra/bluetoothsdk/utils/PhoneAudioMonitor.kt +45 -0
  38. package/android/src/main/java/com/mentra/bluetoothsdk/utils/audio/Ar99OpusPcmDecoder.java +398 -0
  39. package/android/src/main/java/com/mentra/bluetoothsdk/utils/audio/PcmStreamPlayer.kt +328 -0
  40. package/android/src/test/java/com/mentra/bluetoothsdk/OtaManifestDowngradeTest.kt +86 -0
  41. package/android/src/test/java/com/mentra/bluetoothsdk/StreamStatusEventTest.kt +29 -0
  42. package/android/src/test/java/com/mentra/bluetoothsdk/camera/PhotoRequestTest.kt +45 -0
  43. package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/Ar99AdvertisementMatcherTest.java +22 -0
  44. package/android/src/test/java/com/mentra/bluetoothsdk/utils/BleJsonCompactTest.java +20 -0
  45. package/android/src/test/java/com/mentra/bluetoothsdk/utils/PhoneAudioMonitorTest.kt +45 -0
  46. package/build/BluetoothSdk.types.d.ts +100 -22
  47. package/build/BluetoothSdk.types.d.ts.map +1 -1
  48. package/build/BluetoothSdk.types.js +2 -1
  49. package/build/BluetoothSdk.types.js.map +1 -1
  50. package/build/_internal.d.ts +3 -0
  51. package/build/_internal.d.ts.map +1 -1
  52. package/build/_internal.js +3 -0
  53. package/build/_internal.js.map +1 -1
  54. package/build/_private/BluetoothSdkModule.d.ts +31 -4
  55. package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
  56. package/build/_private/BluetoothSdkModule.js +26 -3
  57. package/build/_private/BluetoothSdkModule.js.map +1 -1
  58. package/build/_private/MentraLocalNetworkModule.d.ts +37 -0
  59. package/build/_private/MentraLocalNetworkModule.d.ts.map +1 -0
  60. package/build/_private/MentraLocalNetworkModule.js +3 -0
  61. package/build/_private/MentraLocalNetworkModule.js.map +1 -0
  62. package/build/_private/cameraRequestPayload.d.ts +1 -1
  63. package/build/_private/cameraRequestPayload.d.ts.map +1 -1
  64. package/build/_private/cameraRequestPayload.js +7 -0
  65. package/build/_private/cameraRequestPayload.js.map +1 -1
  66. package/build/_private/photoRequestPayload.d.ts.map +1 -1
  67. package/build/_private/photoRequestPayload.js +10 -0
  68. package/build/_private/photoRequestPayload.js.map +1 -1
  69. package/build/_private/sdkOtaManifest.d.ts +20 -0
  70. package/build/_private/sdkOtaManifest.d.ts.map +1 -0
  71. package/build/_private/sdkOtaManifest.js +28 -0
  72. package/build/_private/sdkOtaManifest.js.map +1 -0
  73. package/build/index.d.ts +1 -1
  74. package/build/index.d.ts.map +1 -1
  75. package/build/index.js +11 -1
  76. package/build/index.js.map +1 -1
  77. package/expo-module.config.json +1 -0
  78. package/ios/BluetoothSdkModule.swift +113 -2
  79. package/ios/Source/BluetoothSdkDefaults.swift +2 -1
  80. package/ios/Source/Bridge.swift +87 -21
  81. package/ios/Source/DeviceManager.swift +118 -20
  82. package/ios/Source/DeviceStore.swift +4 -0
  83. package/ios/Source/MentraBluetoothSDK.swift +326 -33
  84. package/ios/Source/OtaManifest.swift +47 -4
  85. package/ios/Source/audio/PcmStreamPlayer.swift +410 -0
  86. package/ios/Source/camera/CameraModels.swift +26 -6
  87. package/ios/Source/internal/BleTraceLogger.swift +1 -1
  88. package/ios/Source/internal/BluetoothSdkAnalytics.swift +39 -0
  89. package/ios/Source/sgcs/Ar99.swift +2451 -0
  90. package/ios/Source/sgcs/G1.swift +1 -1
  91. package/ios/Source/sgcs/G2.swift +19 -7
  92. package/ios/Source/sgcs/Mach1.swift +1 -1
  93. package/ios/Source/sgcs/MentraLive.swift +478 -61
  94. package/ios/Source/sgcs/MentraNex.swift +1 -1
  95. package/ios/Source/sgcs/Nimo.swift +1 -1
  96. package/ios/Source/sgcs/SGCManager.swift +13 -2
  97. package/ios/Source/sgcs/Simulated.swift +1 -1
  98. package/ios/Source/sgcs/ar99/ota/Ar99OtaManager.swift +637 -0
  99. package/ios/Source/status/WifiHotspotStatus.swift +21 -2
  100. package/ios/Source/streaming/StreamModels.swift +68 -2
  101. package/ios/Source/tts/TTSTools.swift +1 -1
  102. package/ios/Source/types/DeviceModels.swift +13 -0
  103. package/ios/Source/utils/BleJsonCompact.swift +8 -0
  104. package/ios/Source/utils/Constants.swift +2 -0
  105. package/ios/Source/utils/PhoneAudioMonitor.swift +4 -0
  106. package/package.json +1 -2
  107. package/src/BluetoothSdk.types.ts +106 -24
  108. package/src/_internal.ts +3 -0
  109. package/src/_private/BluetoothSdkModule.ts +83 -18
  110. package/src/_private/MentraLocalNetworkModule.ts +50 -0
  111. package/src/_private/cameraRequestPayload.ts +9 -2
  112. package/src/_private/photoRequestPayload.ts +13 -4
  113. package/src/_private/sdkOtaManifest.ts +31 -0
  114. package/src/index.ts +13 -1
@@ -11,6 +11,7 @@ import {
11
11
  CAMERA_FOV_MIN,
12
12
  CameraFovPreset,
13
13
  CameraFovRequest,
14
+ CameraFovOverrideRequest,
14
15
  CameraFovResult,
15
16
  CameraFovSetting,
16
17
  CameraRoiPosition,
@@ -116,7 +117,7 @@ declare class BluetoothSdkNativeModule extends NativeModule<BluetoothSdkModuleEv
116
117
  sendWifiCredentials(ssid: string, password: string): Promise<WifiStatusChangeEvent>
117
118
  forgetWifiNetwork(ssid: string): Promise<WifiStatusChangeEvent>
118
119
  setHotspotState(enabled: boolean): Promise<HotspotStatusChangeEvent>
119
- /** Set glasses system clock (Mentra Live only) when phone detects clock skew. */
120
+ /** Set the glasses system clock when phone detects clock skew (Mentra Live and G2). */
120
121
  setSystemTime(timestampMs: number): Promise<void>
121
122
  /** Logs current WiFi frequency (MHz) and 5 GHz band to Android logcat. */
122
123
  logCurrentWifiFrequency(): Promise<void>
@@ -124,14 +125,20 @@ declare class BluetoothSdkNativeModule extends NativeModule<BluetoothSdkModuleEv
124
125
  // Gallery Commands
125
126
  setGalleryModeEnabled(enabled: boolean): Promise<SettingsAckSuccessEvent>
126
127
  setVoiceActivityDetectionEnabled(enabled: boolean): Promise<void>
128
+ /** Mentra Live center-mic loudness / Barrier gate (cs_swit type 10). */
129
+ setLoudnessGateEnabled(enabled: boolean): Promise<void>
127
130
  /**
128
131
  * @deprecated Sticky action-button photo presets are deprecated. Prefer per-request
129
- * `requestPhoto(...)` options (e.g. `mode: "text"` for AE ÷3).
132
+ * `requestPhoto(...)` options (e.g. `mode: "text"` for text sensor size/crop).
130
133
  */
131
134
  setPhotoCaptureDefaults(settings: PhotoCaptureDefaults): Promise<SettingsAckSuccessEvent>
132
135
  setVideoRecordingDefaults(width: number, height: number, fps: number): Promise<SettingsAckSuccessEvent>
133
136
  setMaxVideoRecordingDuration(minutes: number): Promise<SettingsAckSuccessEvent>
134
137
  setCameraFov(request: CameraFovRequest): Promise<CameraFovResult>
138
+ setLegacyCameraFov(request: CameraFovRequest): Promise<CameraFovResult>
139
+ restoreLegacyCameraFov(): Promise<void>
140
+ setCameraFovOverride(request: CameraFovOverrideRequest): Promise<CameraFovResult>
141
+ releaseCameraFovOverride(leaseId: string): Promise<SettingsAckSuccessEvent>
135
142
  /**
136
143
  * Configure camera HAL tuning (ANR / gain) on Mentra Live glasses.
137
144
  * Sends a {@code camera_tuning_config} BLE message; the ASG client relays it as a
@@ -144,6 +151,7 @@ declare class BluetoothSdkNativeModule extends NativeModule<BluetoothSdkModuleEv
144
151
  queryGalleryStatus(): Promise<GalleryStatusEvent>
145
152
  requestPhoto(params: PhotoRequestParams): Promise<PhotoSuccessResponseEvent>
146
153
  warmUpCamera(params: WarmUpCameraParams): Promise<CameraStatusEvent>
154
+ stopCameraWarmUp(requestId: string): Promise<void>
147
155
 
148
156
  // OTA Commands
149
157
  setOtaVersionUrl(otaVersionUrl: string): void
@@ -151,6 +159,10 @@ declare class BluetoothSdkNativeModule extends NativeModule<BluetoothSdkModuleEv
151
159
  checkForOtaUpdate(): Promise<boolean>
152
160
  startOtaUpdate(otaVersionUrl?: string | null): Promise<OtaStartAckEvent>
153
161
  sendOtaQueryStatus(): Promise<OtaQueryResult>
162
+ startAr99OtaFromFile(path: string): Promise<boolean>
163
+ cancelAr99Ota(): Promise<void>
164
+ sendAr99FactoryReset(): Promise<void>
165
+ buildAr99OtaSignature(secret: string, appName: string, currentVersion: string, serialNumber: string, nonce: string): string
154
166
 
155
167
  // Version Info Commands
156
168
  requestVersionInfo(): Promise<VersionInfoResult>
@@ -184,9 +196,26 @@ declare class BluetoothSdkNativeModule extends NativeModule<BluetoothSdkModuleEv
184
196
  // Used to suspend LC3 mic during audio playback to avoid MCU overload
185
197
  setOwnAppAudioPlaying(playing: boolean): Promise<void>
186
198
 
199
+ // Live PCM output stream (miniapp speaker.createStream). MentraOS-internal
200
+ // ? 16-bit LE PCM chunks into a streaming AudioTrack (USAGE_MEDIA, so it
201
+ // follows the phone's media route, e.g. A2DP to glasses). Implemented with
202
+ // AudioTrack on Android and AVAudioEngine on iOS.
203
+ /** Open a PCM stream session. One AudioTrack per id; caller manages ids. */
204
+ pcmStreamOpen(streamId: string, sampleRate: number, channels: number, volume: number): Promise<void>
205
+ /**
206
+ * Append base64 PCM. Resolves with the queued-but-unplayed backlog in ms;
207
+ * blocks (on a background dispatcher) while the backlog is above the
208
+ * backpressure ceiling, so awaited writes self-throttle to realtime.
209
+ */
210
+ pcmStreamWrite(streamId: string, base64: string): Promise<{bufferedMs: number}>
211
+ /** Drain the backlog, then stop. Resolves with the total played duration. */
212
+ pcmStreamClose(streamId: string): Promise<{durationMs: number}>
213
+ /** Stop immediately, dropping any backlog. Idempotent. */
214
+ pcmStreamAbort(streamId: string): Promise<void>
215
+
187
216
  /** Mentra Live only: K900 `cs_getvol` / `sr_getvol`. */
188
217
  getGlassesMediaVolume(): Promise<GlassesMediaVolumeGetResult>
189
- /** Mentra Live only: K900 `cs_vol` / `sr_vol`; level clamped 0–15 on native. */
218
+ /** Mentra Live only: K900 `cs_vol` / `sr_vol`; level clamped 0??5 on native. */
190
219
  setGlassesMediaVolume(level: number): Promise<GlassesMediaVolumeSetResult>
191
220
 
192
221
  // RGB LED Control
@@ -244,13 +273,10 @@ const DEFAULT_CONNECT_OPTIONS: Required<ConnectOptions> = {
244
273
 
245
274
  const DEFAULT_SCAN_TIMEOUT_MS = 15_000
246
275
 
247
- function bindNativeMethod<T extends (...args: never[]) => unknown>(
248
- module: Record<string, unknown>,
249
- name: string,
250
- ): T {
276
+ function bindNativeMethod<T extends (...args: never[]) => unknown>(module: Record<string, unknown>, name: string): T {
251
277
  const method = module[name]
252
278
  if (typeof method !== "function") {
253
- console.warn(`[BluetoothSdk] Native method "${name}" is unavailable rebuild the app (bun android / bun ios)`)
279
+ console.warn(`[BluetoothSdk] Native method "${name}" is unavailable ??rebuild the app (bun android / bun ios)`)
254
280
  return (async () => {
255
281
  throw new Error(`BluetoothSdk.${name} is not available in this native build. Rebuild the app.`)
256
282
  }) as T
@@ -267,10 +293,10 @@ const CAMERA_ROI_POSITION_VALUES: Record<CameraRoiPosition, CameraFovSetting["ro
267
293
  }
268
294
 
269
295
  // Named presets are a convenience layer over the numeric {fov, roiPosition} API.
270
- // "narrow" uses 82, a device-tested FOV; "standard" matches CAMERA_FOV_DEFAULT.
296
+ // The default is the full sensor; "standard" preserves the historical 102� crop.
271
297
  const CAMERA_FOV_PRESETS: Record<CameraFovPreset, CameraFovSetting> = {
272
298
  narrow: {fov: 82, roiPosition: 0},
273
- standard: {fov: CAMERA_FOV_DEFAULT, roiPosition: 0},
299
+ standard: {fov: 102, roiPosition: 0},
274
300
  wide: {fov: CAMERA_FOV_MAX, roiPosition: 0},
275
301
  }
276
302
 
@@ -286,11 +312,7 @@ function normalizeCameraFov(request: CameraFovRequest): CameraFovSetting {
286
312
  const roiPosition = request.roiPosition ?? "center"
287
313
 
288
314
  return {
289
- fov: clampInteger(
290
- Number.isFinite(request.fov) ? request.fov : CAMERA_FOV_DEFAULT,
291
- CAMERA_FOV_MIN,
292
- CAMERA_FOV_MAX,
293
- ),
315
+ fov: clampInteger(Number.isFinite(request.fov) ? request.fov : CAMERA_FOV_DEFAULT, CAMERA_FOV_MIN, CAMERA_FOV_MAX),
294
316
  roiPosition: clampInteger(CAMERA_ROI_POSITION_VALUES[roiPosition] ?? 0, CAMERA_ROI_MIN, CAMERA_ROI_MAX) as
295
317
  | 0
296
318
  | 1
@@ -470,14 +492,53 @@ NativeBluetoothSdkModule.setVoiceActivityDetectionEnabled = function (enabled: b
470
492
  return this.updateBluetoothSettings({voice_activity_detection_enabled: enabled})
471
493
  }
472
494
 
473
- const nativeSetCameraFov = bindNativeMethod<
474
- (fov: CameraFovSetting) => MaybePromise<CameraFovResult>
475
- >(NativeBluetoothSdkModule as unknown as Record<string, unknown>, "setCameraFov")
495
+ NativeBluetoothSdkModule.setLoudnessGateEnabled = function (enabled: boolean) {
496
+ return this.updateBluetoothSettings({loudness_gate_enabled: enabled})
497
+ }
498
+
499
+ const nativeSetCameraFov = bindNativeMethod<(fov: CameraFovSetting) => MaybePromise<CameraFovResult>>(
500
+ NativeBluetoothSdkModule as unknown as Record<string, unknown>,
501
+ "setCameraFov",
502
+ )
476
503
  NativeBluetoothSdkModule.setCameraFov = function (request: CameraFovRequest) {
477
504
  const setting = normalizeCameraFov(request)
478
505
  return Promise.resolve(nativeSetCameraFov(setting))
479
506
  }
480
507
 
508
+ const nativeSetLegacyCameraFov = bindNativeMethod<(fov: CameraFovSetting) => MaybePromise<CameraFovResult>>(
509
+ NativeBluetoothSdkModule as unknown as Record<string, unknown>,
510
+ "setLegacyCameraFov",
511
+ )
512
+ NativeBluetoothSdkModule.setLegacyCameraFov = function (request: CameraFovRequest) {
513
+ return Promise.resolve(nativeSetLegacyCameraFov(normalizeCameraFov(request)))
514
+ }
515
+
516
+ const nativeRestoreLegacyCameraFov = bindNativeMethod<() => MaybePromise<void>>(
517
+ NativeBluetoothSdkModule as unknown as Record<string, unknown>,
518
+ "restoreLegacyCameraFov",
519
+ )
520
+ NativeBluetoothSdkModule.restoreLegacyCameraFov = function () {
521
+ return Promise.resolve(nativeRestoreLegacyCameraFov())
522
+ }
523
+
524
+ const nativeSetCameraFovOverride = bindNativeMethod<
525
+ (request: CameraFovSetting & {leaseId: string; ttlMs: number}) => MaybePromise<CameraFovResult>
526
+ >(NativeBluetoothSdkModule as unknown as Record<string, unknown>, "setCameraFovOverride")
527
+ NativeBluetoothSdkModule.setCameraFovOverride = function (request: CameraFovOverrideRequest) {
528
+ const setting = normalizeCameraFov(request)
529
+ const requestedTtl =
530
+ typeof request.ttlMs === "number" && Number.isFinite(request.ttlMs) && request.ttlMs > 0
531
+ ? Math.round(request.ttlMs)
532
+ : 300_000
533
+ return Promise.resolve(
534
+ nativeSetCameraFovOverride({
535
+ ...setting,
536
+ leaseId: request.leaseId,
537
+ ttlMs: Math.min(requestedTtl, 600_000),
538
+ }),
539
+ )
540
+ }
541
+
481
542
  NativeBluetoothSdkModule.setMicState = function (
482
543
  enabled: boolean,
483
544
  useGlassesMic?: boolean,
@@ -588,3 +649,7 @@ NativeBluetoothSdkModule.warmUpCamera = function (params: WarmUpCameraParams) {
588
649
 
589
650
  export default NativeBluetoothSdkModule
590
651
  export const BluetoothSdk = NativeBluetoothSdkModule as BluetoothSdkInternalModule
652
+
653
+
654
+
655
+
@@ -0,0 +1,50 @@
1
+ import {NativeModule, requireOptionalNativeModule} from "expo"
2
+
3
+ export type LocalNetworkResponse = {
4
+ status: number
5
+ headers: Record<string, string>
6
+ bodyBase64: string
7
+ }
8
+
9
+ export type LocalNetworkDownloadResult = {
10
+ statusCode: number
11
+ bytesWritten: number
12
+ headers: Record<string, string>
13
+ }
14
+
15
+ export type LocalNetworkDownloadProgress = {
16
+ requestId: string
17
+ bytesWritten: number
18
+ contentLength: number
19
+ statusCode?: number
20
+ headers?: Record<string, string>
21
+ }
22
+
23
+ type LocalNetworkEvents = {
24
+ downloadProgress: (event: LocalNetworkDownloadProgress) => void
25
+ networkLost: (event: {ssid: string}) => void
26
+ }
27
+
28
+ declare class MentraLocalNetworkNativeModule extends NativeModule<LocalNetworkEvents> {
29
+ connect(ssid: string, password: string): Promise<{connected: boolean; ssid: string}>
30
+ request(
31
+ requestId: string,
32
+ url: string,
33
+ method: string,
34
+ headers: Record<string, string>,
35
+ body: string | null,
36
+ timeoutMs: number,
37
+ ): Promise<LocalNetworkResponse>
38
+ download(
39
+ requestId: string,
40
+ url: string,
41
+ destination: string,
42
+ headers: Record<string, string>,
43
+ connectionTimeoutMs: number,
44
+ readTimeoutMs: number,
45
+ ): Promise<LocalNetworkDownloadResult>
46
+ cancel(requestId: string): Promise<void>
47
+ disconnect(): Promise<void>
48
+ }
49
+
50
+ export default requireOptionalNativeModule<MentraLocalNetworkNativeModule>("MentraLocalNetwork")
@@ -9,9 +9,10 @@ function nonBlankString(value?: string | null): string | undefined {
9
9
  /** Expo Android bridge rejects null values in Map<String, Any> — omit optional nullish fields. */
10
10
  export function warmUpCameraParamsForNative(
11
11
  params: WarmUpCameraParams,
12
- ): Record<string, string | number> {
13
- const payload: Record<string, string | number> = {
12
+ ): Record<string, string | number | boolean> {
13
+ const payload: Record<string, string | number | boolean> = {
14
14
  size: normalizePhotoSizeTier(params.size),
15
+ mode: params.mode ?? "photo",
15
16
  }
16
17
  const requestId = nonBlankString(params.requestId)
17
18
  if (requestId != null) {
@@ -25,5 +26,11 @@ export function warmUpCameraParamsForNative(
25
26
  if (durationMs != null && Number.isFinite(durationMs) && durationMs > 0) {
26
27
  payload.durationMs = Math.round(durationMs)
27
28
  }
29
+ if (params.mfnr != null) {
30
+ payload.mfnr = params.mfnr
31
+ }
32
+ if (params.zsl != null) {
33
+ payload.zsl = params.zsl
34
+ }
28
35
  return payload
29
36
  }
@@ -1,4 +1,14 @@
1
- import type {PhotoRequestParams, PhotoSize} from "../BluetoothSdk.types"
1
+ import type {PhotoRequestParams, PhotoSize, PhotoTransferMethod} from "../BluetoothSdk.types"
2
+
3
+ const PHOTO_TRANSFER_METHODS = new Set<PhotoTransferMethod>(["auto", "direct", "ble"])
4
+
5
+ function photoTransferMethodForNative(value: unknown): PhotoTransferMethod {
6
+ if (value === undefined) return "auto"
7
+ if (typeof value === "string" && PHOTO_TRANSFER_METHODS.has(value as PhotoTransferMethod)) {
8
+ return value as PhotoTransferMethod
9
+ }
10
+ throw new TypeError(`Invalid transferMethod ${JSON.stringify(value)}. Expected "auto", "direct", or "ble".`)
11
+ }
2
12
 
3
13
  /** Maps unknown/legacy size strings to the current wire format. */
4
14
  export function normalizePhotoSizeTier(size: string | undefined): PhotoSize {
@@ -25,12 +35,11 @@ function nonBlankString(value?: string | null): string | undefined {
25
35
  }
26
36
 
27
37
  /** Expo Android bridge rejects null values in Map<String, Any> — omit optional nullish fields. */
28
- export function photoRequestParamsForNative(
29
- params: PhotoRequestParams,
30
- ): Record<string, string | number | boolean> {
38
+ export function photoRequestParamsForNative(params: PhotoRequestParams): Record<string, string | number | boolean> {
31
39
  const payload: Record<string, string | number | boolean> = {
32
40
  size: normalizePhotoSizeTier(params.size),
33
41
  mode: params.mode ?? "photo",
42
+ transferMethod: photoTransferMethodForNative(params.transferMethod),
34
43
  webhookUrl: params.webhookUrl ?? "",
35
44
  compress: params.compress,
36
45
  sound: params.sound,
@@ -0,0 +1,31 @@
1
+ /**
2
+ * SDK-pinned OTA manifest URL derivation.
3
+ *
4
+ * Each Bluetooth SDK release publishes an immutable manifest
5
+ * (`bluetooth-sdk-<version>-version.json`) pinning the exact ASG artifact paired with that SDK
6
+ * version. The URL is derived from this package's own version — the version is frozen into the
7
+ * published artifact, so this is equivalent to baking the URL at release time while keeping the
8
+ * manifest itself replaceable as an operational escape hatch (asset moves, CDN migration).
9
+ *
10
+ * Mirrors the native derivation in `OtaManifestDefaults.defaultOtaVersionUrl()` (Kotlin) /
11
+ * `BluetoothSdkDefaults` (Swift).
12
+ */
13
+
14
+ const SDK_OTA_RELEASE_BASE_URL = "https://github.com/Mentra-Community/MentraOS/releases/download/bluetooth-sdk-ota"
15
+
16
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
17
+ const packageJson = require("../../package.json") as {version?: string}
18
+
19
+ /** This Bluetooth SDK package's own version, as published. */
20
+ export const BLUETOOTH_SDK_VERSION: string = (packageJson.version ?? "").trim()
21
+
22
+ /**
23
+ * The immutable manifest URL pinning the ASG artifact paired with this SDK version, or null when
24
+ * the package version is unavailable (malformed workspace state).
25
+ */
26
+ export function sdkPinnedOtaManifestUrl(): string | null {
27
+ if (!BLUETOOTH_SDK_VERSION) {
28
+ return null
29
+ }
30
+ return `${SDK_OTA_RELEASE_BASE_URL}/bluetooth-sdk-${BLUETOOTH_SDK_VERSION}-version.json`
31
+ }
package/src/index.ts CHANGED
@@ -44,6 +44,7 @@ const PUBLIC_EVENT_NAMES = new Set<BluetoothSdkEventName>([
44
44
  "stream_status",
45
45
  "ota_start_ack",
46
46
  "ota_status",
47
+ "ar99_ota_status",
47
48
  "version_info",
48
49
  "extraction_progress",
49
50
  ])
@@ -96,9 +97,10 @@ export const BluetoothSdk: BluetoothSdkPublicModule = Object.freeze({
96
97
  setHotspotState: bindPublicMethod("setHotspotState"),
97
98
  setGalleryModeEnabled: bindPublicMethod("setGalleryModeEnabled"),
98
99
  setVoiceActivityDetectionEnabled: bindPublicMethod("setVoiceActivityDetectionEnabled"),
100
+ setLoudnessGateEnabled: bindPublicMethod("setLoudnessGateEnabled"),
99
101
  /**
100
102
  * @deprecated Sticky action-button photo presets are deprecated. Prefer per-request
101
- * `requestPhoto(...)` options (e.g. `mode: "text"` for AE ÷3, or explicit per-shot
103
+ * `requestPhoto(...)` options (e.g. `mode: "text"` for text sensor size/crop, or explicit per-shot
102
104
  * fields). Still functional until removed in a future release.
103
105
  */
104
106
  setPhotoCaptureDefaults: bindPublicMethod("setPhotoCaptureDefaults"),
@@ -113,10 +115,14 @@ export const BluetoothSdk: BluetoothSdkPublicModule = Object.freeze({
113
115
  },
114
116
  setMaxVideoRecordingDuration: bindPublicMethod("setMaxVideoRecordingDuration"),
115
117
  setCameraFov: bindPublicMethod("setCameraFov"),
118
+ setLegacyCameraFov: bindPublicMethod("setLegacyCameraFov"),
119
+ setCameraFovOverride: bindPublicMethod("setCameraFovOverride"),
120
+ releaseCameraFovOverride: bindPublicMethod("releaseCameraFovOverride"),
116
121
  setCameraTuningConfig: bindPublicMethod("setCameraTuningConfig"),
117
122
  queryGalleryStatus: bindPublicMethod("queryGalleryStatus"),
118
123
  requestPhoto: bindPublicMethod("requestPhoto"),
119
124
  warmUpCamera: bindPublicMethod("warmUpCamera"),
125
+ stopCameraWarmUp: bindPublicMethod("stopCameraWarmUp"),
120
126
  startVideoRecording: bindPublicMethod("startVideoRecording"),
121
127
  stopVideoRecording: bindPublicMethod("stopVideoRecording"),
122
128
  startStream: bindPublicMethod("startStream"),
@@ -130,6 +136,10 @@ export const BluetoothSdk: BluetoothSdkPublicModule = Object.freeze({
130
136
  requestVersionInfo: bindPublicMethod("requestVersionInfo"),
131
137
  checkForOtaUpdate: bindPublicMethod("checkForOtaUpdate"),
132
138
  startOtaUpdate,
139
+ startAr99OtaFromFile: bindPublicMethod("startAr99OtaFromFile"),
140
+ cancelAr99Ota: bindPublicMethod("cancelAr99Ota"),
141
+ sendAr99FactoryReset: bindPublicMethod("sendAr99FactoryReset"),
142
+ buildAr99OtaSignature: bindPublicMethod("buildAr99OtaSignature"),
133
143
  setSttModelDetails: bindPublicMethod("setSttModelDetails"),
134
144
  getSttModelPath: bindPublicMethod("getSttModelPath"),
135
145
  checkSttModelAvailable: bindPublicMethod("checkSttModelAvailable"),
@@ -160,6 +170,7 @@ export {
160
170
 
161
171
  export type {
162
172
  AccelEvent,
173
+ Ar99OtaStatusEvent,
163
174
  AudioConnectedEvent,
164
175
  AudioDisconnectedEvent,
165
176
  AudioPairingNeededEvent,
@@ -210,6 +221,7 @@ export type {
210
221
  PhotoFpsRange,
211
222
  PhotoMeteredPreview,
212
223
  PhotoMode,
224
+ PhotoTransferMethod,
213
225
  PhotoResponseEvent,
214
226
  PhotoRequestedCaptureConfig,
215
227
  PhotoRequestParams,