@mentra/bluetooth-sdk 0.1.19 → 0.1.21-beta.0

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 (83) hide show
  1. package/README.md +6 -2
  2. package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +239 -52
  3. package/android/src/main/java/com/mentra/bluetoothsdk/Bridge.kt +65 -14
  4. package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +47 -4
  5. package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +6 -1
  6. package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +381 -97
  7. package/android/src/main/java/com/mentra/bluetoothsdk/MentraLocalNetworkModule.kt +333 -0
  8. package/android/src/main/java/com/mentra/bluetoothsdk/camera/CameraModels.kt +16 -0
  9. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G1.kt +2 -2
  10. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G1TextSanitizer.kt +38 -0
  11. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G2.kt +16 -9
  12. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Mach1.kt +1 -1
  13. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.kt +293 -174
  14. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraNex.kt +1 -1
  15. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Nimo.kt +1 -1
  16. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/SGCManager.kt +4 -2
  17. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Simulated.kt +1 -1
  18. package/android/src/main/java/com/mentra/bluetoothsdk/status/WifiHotspotStatus.kt +19 -3
  19. package/android/src/main/java/com/mentra/bluetoothsdk/streaming/StreamModels.kt +15 -2
  20. package/android/src/main/java/com/mentra/bluetoothsdk/tts/TTSTools.kt +115 -47
  21. package/android/src/main/java/com/mentra/bluetoothsdk/types/DeviceModels.kt +2 -3
  22. package/android/src/main/java/com/mentra/bluetoothsdk/utils/BlePhotoUploadService.java +65 -8
  23. package/android/src/main/java/com/mentra/bluetoothsdk/utils/K900ProtocolUtils.java +6 -5
  24. package/android/src/main/java/com/mentra/bluetoothsdk/utils/audio/PcmStreamPlayer.kt +328 -0
  25. package/android/src/test/java/com/mentra/bluetoothsdk/BluetoothSdkExceptionTest.kt +18 -0
  26. package/android/src/test/java/com/mentra/bluetoothsdk/camera/PhotoRequestTest.kt +31 -0
  27. package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/G1TextSanitizerTest.kt +31 -0
  28. package/android/src/test/java/com/mentra/bluetoothsdk/utils/BlePhotoUploadServiceTest.java +9 -0
  29. package/build/BluetoothSdk.types.d.ts +71 -1
  30. package/build/BluetoothSdk.types.d.ts.map +1 -1
  31. package/build/BluetoothSdk.types.js.map +1 -1
  32. package/build/_internal.d.ts +2 -0
  33. package/build/_internal.d.ts.map +1 -1
  34. package/build/_internal.js +2 -0
  35. package/build/_internal.js.map +1 -1
  36. package/build/_private/BluetoothSdkModule.d.ts +27 -2
  37. package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
  38. package/build/_private/BluetoothSdkModule.js +20 -0
  39. package/build/_private/BluetoothSdkModule.js.map +1 -1
  40. package/build/_private/MentraLocalNetworkModule.d.ts +37 -0
  41. package/build/_private/MentraLocalNetworkModule.d.ts.map +1 -0
  42. package/build/_private/MentraLocalNetworkModule.js +3 -0
  43. package/build/_private/MentraLocalNetworkModule.js.map +1 -0
  44. package/build/_private/cameraRequestPayload.d.ts.map +1 -1
  45. package/build/_private/cameraRequestPayload.js +1 -0
  46. package/build/_private/cameraRequestPayload.js.map +1 -1
  47. package/build/_private/photoRequestPayload.d.ts.map +1 -1
  48. package/build/_private/photoRequestPayload.js +2 -0
  49. package/build/_private/photoRequestPayload.js.map +1 -1
  50. package/build/index.d.ts +1 -1
  51. package/build/index.d.ts.map +1 -1
  52. package/build/index.js +9 -0
  53. package/build/index.js.map +1 -1
  54. package/expo-module.config.json +1 -0
  55. package/ios/BluetoothSdkModule.swift +72 -0
  56. package/ios/Source/BluetoothSdkDefaults.swift +1 -1
  57. package/ios/Source/Bridge.swift +72 -18
  58. package/ios/Source/DeviceManager.swift +52 -3
  59. package/ios/Source/DeviceStore.swift +6 -1
  60. package/ios/Source/MentraBluetoothSDK.swift +298 -21
  61. package/ios/Source/audio/PcmStreamPlayer.swift +410 -0
  62. package/ios/Source/camera/CameraModels.swift +22 -3
  63. package/ios/Source/sgcs/G1.swift +2 -2
  64. package/ios/Source/sgcs/G2.swift +19 -7
  65. package/ios/Source/sgcs/Mach1.swift +1 -1
  66. package/ios/Source/sgcs/MentraLive.swift +456 -52
  67. package/ios/Source/sgcs/MentraLiveL2capChannel.swift +183 -0
  68. package/ios/Source/sgcs/MentraNex.swift +1 -1
  69. package/ios/Source/sgcs/Nimo.swift +1 -1
  70. package/ios/Source/sgcs/SGCManager.swift +5 -2
  71. package/ios/Source/sgcs/Simulated.swift +1 -1
  72. package/ios/Source/status/WifiHotspotStatus.swift +21 -2
  73. package/ios/Source/streaming/StreamModels.swift +9 -0
  74. package/ios/Source/tts/TTSTools.swift +1 -1
  75. package/ios/Source/utils/G1Text.swift +30 -1
  76. package/package.json +1 -2
  77. package/src/BluetoothSdk.types.ts +75 -0
  78. package/src/_internal.ts +2 -0
  79. package/src/_private/BluetoothSdkModule.ts +68 -13
  80. package/src/_private/MentraLocalNetworkModule.ts +50 -0
  81. package/src/_private/cameraRequestPayload.ts +1 -0
  82. package/src/_private/photoRequestPayload.ts +3 -3
  83. package/src/index.ts +10 -0
@@ -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,10 +125,18 @@ 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
+ /**
129
+ * @deprecated Sticky action-button photo presets are deprecated. Prefer per-request
130
+ * `requestPhoto(...)` options (e.g. `mode: "text"` for AE ÷3).
131
+ */
127
132
  setPhotoCaptureDefaults(settings: PhotoCaptureDefaults): Promise<SettingsAckSuccessEvent>
128
133
  setVideoRecordingDefaults(width: number, height: number, fps: number): Promise<SettingsAckSuccessEvent>
129
134
  setMaxVideoRecordingDuration(minutes: number): Promise<SettingsAckSuccessEvent>
130
135
  setCameraFov(request: CameraFovRequest): Promise<CameraFovResult>
136
+ setLegacyCameraFov(request: CameraFovRequest): Promise<CameraFovResult>
137
+ restoreLegacyCameraFov(): Promise<void>
138
+ setCameraFovOverride(request: CameraFovOverrideRequest): Promise<CameraFovResult>
139
+ releaseCameraFovOverride(leaseId: string): Promise<SettingsAckSuccessEvent>
131
140
  /**
132
141
  * Configure camera HAL tuning (ANR / gain) on Mentra Live glasses.
133
142
  * Sends a {@code camera_tuning_config} BLE message; the ASG client relays it as a
@@ -140,6 +149,7 @@ declare class BluetoothSdkNativeModule extends NativeModule<BluetoothSdkModuleEv
140
149
  queryGalleryStatus(): Promise<GalleryStatusEvent>
141
150
  requestPhoto(params: PhotoRequestParams): Promise<PhotoSuccessResponseEvent>
142
151
  warmUpCamera(params: WarmUpCameraParams): Promise<CameraStatusEvent>
152
+ stopCameraWarmUp(requestId: string): Promise<void>
143
153
 
144
154
  // OTA Commands
145
155
  setOtaVersionUrl(otaVersionUrl: string): void
@@ -180,6 +190,23 @@ declare class BluetoothSdkNativeModule extends NativeModule<BluetoothSdkModuleEv
180
190
  // Used to suspend LC3 mic during audio playback to avoid MCU overload
181
191
  setOwnAppAudioPlaying(playing: boolean): Promise<void>
182
192
 
193
+ // Live PCM output stream (miniapp speaker.createStream). MentraOS-internal
194
+ // — 16-bit LE PCM chunks into a streaming AudioTrack (USAGE_MEDIA, so it
195
+ // follows the phone's media route, e.g. A2DP to glasses). Implemented with
196
+ // AudioTrack on Android and AVAudioEngine on iOS.
197
+ /** Open a PCM stream session. One AudioTrack per id; caller manages ids. */
198
+ pcmStreamOpen(streamId: string, sampleRate: number, channels: number, volume: number): Promise<void>
199
+ /**
200
+ * Append base64 PCM. Resolves with the queued-but-unplayed backlog in ms;
201
+ * blocks (on a background dispatcher) while the backlog is above the
202
+ * backpressure ceiling, so awaited writes self-throttle to realtime.
203
+ */
204
+ pcmStreamWrite(streamId: string, base64: string): Promise<{bufferedMs: number}>
205
+ /** Drain the backlog, then stop. Resolves with the total played duration. */
206
+ pcmStreamClose(streamId: string): Promise<{durationMs: number}>
207
+ /** Stop immediately, dropping any backlog. Idempotent. */
208
+ pcmStreamAbort(streamId: string): Promise<void>
209
+
183
210
  /** Mentra Live only: K900 `cs_getvol` / `sr_getvol`. */
184
211
  getGlassesMediaVolume(): Promise<GlassesMediaVolumeGetResult>
185
212
  /** Mentra Live only: K900 `cs_vol` / `sr_vol`; level clamped 0–15 on native. */
@@ -240,10 +267,7 @@ const DEFAULT_CONNECT_OPTIONS: Required<ConnectOptions> = {
240
267
 
241
268
  const DEFAULT_SCAN_TIMEOUT_MS = 15_000
242
269
 
243
- function bindNativeMethod<T extends (...args: never[]) => unknown>(
244
- module: Record<string, unknown>,
245
- name: string,
246
- ): T {
270
+ function bindNativeMethod<T extends (...args: never[]) => unknown>(module: Record<string, unknown>, name: string): T {
247
271
  const method = module[name]
248
272
  if (typeof method !== "function") {
249
273
  console.warn(`[BluetoothSdk] Native method "${name}" is unavailable — rebuild the app (bun android / bun ios)`)
@@ -282,11 +306,7 @@ function normalizeCameraFov(request: CameraFovRequest): CameraFovSetting {
282
306
  const roiPosition = request.roiPosition ?? "center"
283
307
 
284
308
  return {
285
- fov: clampInteger(
286
- Number.isFinite(request.fov) ? request.fov : CAMERA_FOV_DEFAULT,
287
- CAMERA_FOV_MIN,
288
- CAMERA_FOV_MAX,
289
- ),
309
+ fov: clampInteger(Number.isFinite(request.fov) ? request.fov : CAMERA_FOV_DEFAULT, CAMERA_FOV_MIN, CAMERA_FOV_MAX),
290
310
  roiPosition: clampInteger(CAMERA_ROI_POSITION_VALUES[roiPosition] ?? 0, CAMERA_ROI_MIN, CAMERA_ROI_MAX) as
291
311
  | 0
292
312
  | 1
@@ -466,14 +486,49 @@ NativeBluetoothSdkModule.setVoiceActivityDetectionEnabled = function (enabled: b
466
486
  return this.updateBluetoothSettings({voice_activity_detection_enabled: enabled})
467
487
  }
468
488
 
469
- const nativeSetCameraFov = bindNativeMethod<
470
- (fov: CameraFovSetting) => MaybePromise<CameraFovResult>
471
- >(NativeBluetoothSdkModule as unknown as Record<string, unknown>, "setCameraFov")
489
+ const nativeSetCameraFov = bindNativeMethod<(fov: CameraFovSetting) => MaybePromise<CameraFovResult>>(
490
+ NativeBluetoothSdkModule as unknown as Record<string, unknown>,
491
+ "setCameraFov",
492
+ )
472
493
  NativeBluetoothSdkModule.setCameraFov = function (request: CameraFovRequest) {
473
494
  const setting = normalizeCameraFov(request)
474
495
  return Promise.resolve(nativeSetCameraFov(setting))
475
496
  }
476
497
 
498
+ const nativeSetLegacyCameraFov = bindNativeMethod<(fov: CameraFovSetting) => MaybePromise<CameraFovResult>>(
499
+ NativeBluetoothSdkModule as unknown as Record<string, unknown>,
500
+ "setLegacyCameraFov",
501
+ )
502
+ NativeBluetoothSdkModule.setLegacyCameraFov = function (request: CameraFovRequest) {
503
+ return Promise.resolve(nativeSetLegacyCameraFov(normalizeCameraFov(request)))
504
+ }
505
+
506
+ const nativeRestoreLegacyCameraFov = bindNativeMethod<() => MaybePromise<void>>(
507
+ NativeBluetoothSdkModule as unknown as Record<string, unknown>,
508
+ "restoreLegacyCameraFov",
509
+ )
510
+ NativeBluetoothSdkModule.restoreLegacyCameraFov = function () {
511
+ return Promise.resolve(nativeRestoreLegacyCameraFov())
512
+ }
513
+
514
+ const nativeSetCameraFovOverride = bindNativeMethod<
515
+ (request: CameraFovSetting & {leaseId: string; ttlMs: number}) => MaybePromise<CameraFovResult>
516
+ >(NativeBluetoothSdkModule as unknown as Record<string, unknown>, "setCameraFovOverride")
517
+ NativeBluetoothSdkModule.setCameraFovOverride = function (request: CameraFovOverrideRequest) {
518
+ const setting = normalizeCameraFov(request)
519
+ const requestedTtl =
520
+ typeof request.ttlMs === "number" && Number.isFinite(request.ttlMs) && request.ttlMs > 0
521
+ ? Math.round(request.ttlMs)
522
+ : 300_000
523
+ return Promise.resolve(
524
+ nativeSetCameraFovOverride({
525
+ ...setting,
526
+ leaseId: request.leaseId,
527
+ ttlMs: Math.min(requestedTtl, 600_000),
528
+ }),
529
+ )
530
+ }
531
+
477
532
  NativeBluetoothSdkModule.setMicState = function (
478
533
  enabled: boolean,
479
534
  useGlassesMic?: boolean,
@@ -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")
@@ -12,6 +12,7 @@ export function warmUpCameraParamsForNative(
12
12
  ): Record<string, string | number> {
13
13
  const payload: Record<string, string | number> = {
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,11 +25,11 @@ function nonBlankString(value?: string | null): string | undefined {
25
25
  }
26
26
 
27
27
  /** 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> {
28
+ export function photoRequestParamsForNative(params: PhotoRequestParams): Record<string, string | number | boolean> {
31
29
  const payload: Record<string, string | number | boolean> = {
32
30
  size: normalizePhotoSizeTier(params.size),
31
+ mode: params.mode ?? "photo",
32
+ transferMethod: params.transferMethod ?? "auto",
33
33
  webhookUrl: params.webhookUrl ?? "",
34
34
  compress: params.compress,
35
35
  sound: params.sound,
package/src/index.ts CHANGED
@@ -96,6 +96,11 @@ export const BluetoothSdk: BluetoothSdkPublicModule = Object.freeze({
96
96
  setHotspotState: bindPublicMethod("setHotspotState"),
97
97
  setGalleryModeEnabled: bindPublicMethod("setGalleryModeEnabled"),
98
98
  setVoiceActivityDetectionEnabled: bindPublicMethod("setVoiceActivityDetectionEnabled"),
99
+ /**
100
+ * @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
102
+ * fields). Still functional until removed in a future release.
103
+ */
99
104
  setPhotoCaptureDefaults: bindPublicMethod("setPhotoCaptureDefaults"),
100
105
  setVideoRecordingDefaults: ({width, height, fps}: VideoRecordingDefaults) => {
101
106
  const method = (PrivateBluetoothSdkModule as unknown as Record<string, unknown>).setVideoRecordingDefaults
@@ -108,10 +113,14 @@ export const BluetoothSdk: BluetoothSdkPublicModule = Object.freeze({
108
113
  },
109
114
  setMaxVideoRecordingDuration: bindPublicMethod("setMaxVideoRecordingDuration"),
110
115
  setCameraFov: bindPublicMethod("setCameraFov"),
116
+ setLegacyCameraFov: bindPublicMethod("setLegacyCameraFov"),
117
+ setCameraFovOverride: bindPublicMethod("setCameraFovOverride"),
118
+ releaseCameraFovOverride: bindPublicMethod("releaseCameraFovOverride"),
111
119
  setCameraTuningConfig: bindPublicMethod("setCameraTuningConfig"),
112
120
  queryGalleryStatus: bindPublicMethod("queryGalleryStatus"),
113
121
  requestPhoto: bindPublicMethod("requestPhoto"),
114
122
  warmUpCamera: bindPublicMethod("warmUpCamera"),
123
+ stopCameraWarmUp: bindPublicMethod("stopCameraWarmUp"),
115
124
  startVideoRecording: bindPublicMethod("startVideoRecording"),
116
125
  stopVideoRecording: bindPublicMethod("stopVideoRecording"),
117
126
  startStream: bindPublicMethod("startStream"),
@@ -204,6 +213,7 @@ export type {
204
213
  PhotoCompression,
205
214
  PhotoFpsRange,
206
215
  PhotoMeteredPreview,
216
+ PhotoMode,
207
217
  PhotoResponseEvent,
208
218
  PhotoRequestedCaptureConfig,
209
219
  PhotoRequestParams,