@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
package/README.md CHANGED
@@ -315,7 +315,7 @@ console.log(ledAck.state)
315
315
  Settings commands that return `SettingsAckSuccessEvent` reject when the ASG reports an error ack. The SDK updates its local settings store only after that ASG ack resolves successfully, so observed SDK state reflects the acknowledged glasses state rather than a queued request. Raw `settings_ack` listener events still use `SettingsAckEvent` because they can include both success and failure statuses. `rgbLedControl(...)` resolves from a successful ASG `rgb_led_control_response` and rejects when the ASG reports `state: "error"`; raw `settings_ack` and `rgb_led_control_response` events remain available through listeners.
316
316
 
317
317
  WiFi, hotspot, and version-info commands resolve from the ASG response path, not local dispatch:
318
- `requestWifiScan()` resolves from the ASG `wifi_scan_result` completion response with the updated scan list, including `[]` when no networks are found. Intermediate `wifi_scan_result` events can arrive with `scanComplete: false` while the glasses stream discovered networks; the final event uses `scanComplete: true`. `sendWifiCredentials()` resolves when the requested SSID is connected, `forgetWifiNetwork()` resolves when that SSID is no longer connected, `setHotspotState()` resolves when the requested hotspot state is reported, and `requestVersionInfo()` resolves from the ASG `version_info` response instead of local store changes.
318
+ `requestWifiScan()` resolves from the ASG `wifi_scan_result` completion response with the updated scan list, including `[]` when no networks are found. Intermediate `wifi_scan_result` events can arrive with `scanComplete: false` while the glasses stream discovered networks; the final event uses `scanComplete: true`. If older glasses stream non-empty scan results but never send the completion event, the request resolves with the accumulated scan list when the request times out. `sendWifiCredentials()` resolves when the requested SSID is connected, `forgetWifiNetwork()` resolves when that SSID is no longer connected, `setHotspotState()` resolves when the requested hotspot state is reported, and `requestVersionInfo()` resolves from the ASG `version_info` response instead of local store changes.
319
319
 
320
320
  React Native narrows returned values to success shapes where the raw listener event can also report errors:
321
321
 
@@ -327,7 +327,7 @@ React Native narrows returned values to success shapes where the raw listener ev
327
327
  | `rgbLedControl(...)` | `RgbLedControlSuccessResponseEvent` with `state: "success"`. | Rejects when raw `rgb_led_control_response.state === "error"` or the response times out. |
328
328
  | `checkForOtaUpdate()` | `boolean`, true when the configured OTA manifest has an ASG APK, MTK, or BES update for the connected glasses; false only when the manifest was checked successfully and no update is available. | Rejects when the glasses are disconnected, version info is unavailable, the manifest cannot be fetched, or the manifest response is invalid/missing required ASG app version fields. |
329
329
 
330
- Android and iOS async APIs use `BluetoothException` / `BluetoothError` for the same error paths. Their returned event structs are the successful response in normal `try`/`await` code, while raw listener/delegate events still include both success and error payloads.
330
+ Android and iOS async APIs use `BluetoothSdkException` / `BluetoothSdkError` for the same error paths. Their returned event structs are the successful response in normal `try`/`await` code, while raw listener/delegate events still include both success and error payloads.
331
331
 
332
332
  `setMicState(true)` defaults to continuous microphone PCM from the glasses. The SDK does not apply phone-side Voice Activity Detection gating to microphone audio events. Glasses-side Voice Activity Detection is disabled by default for public SDK consumers; use `setVoiceActivityDetectionEnabled(true)` when you want supported glasses to gate microphone audio and emit live speaking status. `voice_activity_detection_status` reports whether glasses-side Voice Activity Detection is enabled, and `speaking_status` reports speaking/not-speaking when supported. Microphone events include the latest `voiceActivityDetectionEnabled` value.
333
333
 
@@ -369,8 +369,6 @@ OTA requires Mentra Live glasses firmware that supports the ASG OTA protocol and
369
369
 
370
370
  ```ts
371
371
  const photo = await BluetoothSdk.requestPhoto({
372
- requestId: `photo-${Date.now()}`,
373
- appId: 'com.example.app',
374
372
  size: 'medium',
375
373
  webhookUrl: 'https://api.example.com/mentra/photo',
376
374
  authToken: 'optional-token',
@@ -382,7 +380,7 @@ const photo = await BluetoothSdk.requestPhoto({
382
380
  console.log('photo delivered', photo.photoUrl ?? photo.uploadUrl, photo.fileSizeBytes)
383
381
  ```
384
382
 
385
- `requestPhoto(...)` resolves only after the full photo action reaches terminal success: capture completed and the photo was delivered to the webhook, either directly from the glasses over Wi-Fi or through the phone's Bluetooth fallback relay. It rejects if the ASG reports `state: "error"`, if phone-side fallback upload fails, if the SDK cannot send the command, or if no terminal `photo_response` arrives before the command timeout. Use `photo_status` for intermediate stages such as `accepted`, `configuring`, `capturing`, `captured`, `uploading`, `ble_fallback_compression`, `ready_for_transfer`, and `transferring`; `photo_status` is progress, while `photo_response` is terminal success/error. The raw `photo_response` event stream still includes both success and error events for subscribers. The webhook should accept multipart form data with a `photo` file and `requestId`. If `authToken` is provided, the uploader adds `Authorization: Bearer <token>`. The camera light is always enabled for photo capture.
383
+ `requestPhoto(...)` resolves only after the full photo action reaches terminal success: capture completed and the photo was delivered to the webhook, either directly from the glasses over Wi-Fi or through the phone's Bluetooth fallback relay. If you omit `requestId`, the SDK generates one and the terminal response includes it. It rejects if the ASG reports `state: "error"`, if phone-side fallback upload fails, if the SDK cannot send the command, or if no terminal `photo_response` arrives before the command timeout. Use `photo_status` for intermediate stages such as `accepted`, `configuring`, `capturing`, `captured`, `uploading`, `ble_fallback_compression`, `ready_for_transfer`, and `transferring`; `photo_status` is progress, while `photo_response` is terminal success/error. The raw `photo_response` event stream still includes both success and error events for subscribers. The webhook should accept multipart form data with a `photo` file and `requestId`. If `authToken` is provided, the uploader adds `Authorization: Bearer <token>`. The camera light is always enabled for photo capture.
386
384
 
387
385
  For one-shot manual capture tuning, pass `exposureTimeNs` and `iso` together. `exposureTimeNs` is sensor exposure time in nanoseconds; `iso` is sensor ISO. If `exposureTimeNs` is omitted, `null`, invalid, or unsupported by the connected glasses, the camera uses auto exposure and ignores `iso`.
388
386
 
@@ -477,7 +475,7 @@ For bare native iOS apps, use the public SwiftPM repository:
477
475
  https://github.com/Mentra-Community/mentra-bluetooth-sdk-ios.git
478
476
  ```
479
477
 
480
- Select version `0.1.14`, then add the `MentraBluetoothSDK` product to your app target.
478
+ Select version `0.1.16`, then add the `MentraBluetoothSDK` product to your app target.
481
479
 
482
480
  For local SDK development, add this package folder directly in Xcode:
483
481
 
@@ -116,6 +116,10 @@ class BluetoothSdkModule : Module() {
116
116
  sendEvent("photo_status", event.values)
117
117
  }
118
118
 
119
+ override fun onCameraStatus(event: CameraStatusEvent) {
120
+ sendEvent("camera_status", event.values)
121
+ }
122
+
119
123
  override fun onVideoRecordingStatus(event: VideoRecordingStatusEvent) {
120
124
  sendEvent("video_recording_status", event.values)
121
125
  }
@@ -136,10 +140,6 @@ class BluetoothSdkModule : Module() {
136
140
  sendEvent("keep_alive_ack", event.values)
137
141
  }
138
142
 
139
- override fun onOtaUpdateAvailable(event: OtaUpdateAvailableEvent) {
140
- sendEvent("ota_update_available", event.values)
141
- }
142
-
143
143
  override fun onOtaStartAck(event: OtaStartAckEvent) {
144
144
  sendEvent("ota_start_ack", event.values)
145
145
  }
@@ -183,7 +183,7 @@ class BluetoothSdkModule : Module() {
183
183
 
184
184
  private fun requireSdk(): MentraBluetoothSdk =
185
185
  sdk
186
- ?: throw BluetoothException(
186
+ ?: throw BluetoothSdkException(
187
187
  "sdk_not_initialized",
188
188
  "Bluetooth SDK is not initialized.",
189
189
  )
@@ -216,6 +216,7 @@ class BluetoothSdkModule : Module() {
216
216
  "hotspot_error",
217
217
  "photo_response",
218
218
  "photo_status",
219
+ "camera_status",
219
220
  "video_recording_status",
220
221
  "media_success",
221
222
  "media_error",
@@ -244,7 +245,6 @@ class BluetoothSdkModule : Module() {
244
245
  "stream_status",
245
246
  "keep_alive_ack",
246
247
  "mtk_update_complete",
247
- "ota_update_available",
248
248
  "ota_progress",
249
249
  "ota_start_ack",
250
250
  "ota_status",
@@ -462,6 +462,10 @@ class BluetoothSdkModule : Module() {
462
462
  requireSdk().setCameraFov(CameraFov(value, roiPosition)).values
463
463
  }
464
464
 
465
+ AsyncFunction("setCameraTuningConfig") { anrOn: Boolean, gainOn: Boolean ->
466
+ requireSdk().setCameraTuningConfig(anrOn, gainOn).values
467
+ }
468
+
465
469
  AsyncFunction("queryGalleryStatus") { requireSdk().queryGalleryStatus().values }
466
470
 
467
471
  AsyncFunction("requestPhoto") { params: Map<String, Any?> ->
@@ -472,11 +476,22 @@ class BluetoothSdkModule : Module() {
472
476
  }.toMap()
473
477
  val req = PhotoRequest.fromMap(sanitized)
474
478
  Bridge.log(
475
- "NATIVE: PHOTO PIPELINE [3/6] BluetoothSdk.requestPhoto requestId=${req.requestId} appId=${req.appId} size=${req.size} compress=${req.compress} sound=${req.sound} exposureTimeNs=${req.exposureTimeNs} iso=${req.iso}"
479
+ "NATIVE: PHOTO PIPELINE [3/6] BluetoothSdk.requestPhoto requestId=${req.requestId} size=${req.size} compress=${req.compress} sound=${req.sound} exposureTimeNs=${req.exposureTimeNs} iso=${req.iso}"
476
480
  )
477
481
  requireSdk().requestPhoto(req).values
478
482
  }
479
483
 
484
+ AsyncFunction("warmUpCamera") { params: Map<String, Any?> ->
485
+ val requestId = params["requestId"] as? String
486
+ val size = PhotoSize.fromValue(params["size"] as? String)
487
+ val exposureRaw = (params["exposureTimeNs"] as? Number)?.toDouble()
488
+ val exposureTimeNs =
489
+ if (exposureRaw != null && exposureRaw.isFinite() && exposureRaw > 0) exposureRaw.toLong() else null
490
+ val durationRaw = (params["durationMs"] as? Number)?.toInt() ?: 0
491
+ val durationMs = if (durationRaw > 0) durationRaw else 15000
492
+ requireSdk().warmUpCamera(requestId, size, exposureTimeNs, durationMs).values
493
+ }
494
+
480
495
  // MARK: - OTA Commands
481
496
 
482
497
  Function("setOtaVersionUrl") { otaVersionUrl: String ->
@@ -502,8 +517,6 @@ class BluetoothSdkModule : Module() {
502
517
 
503
518
  AsyncFunction("sendOtaQueryStatus") { requireSdk().sendOtaQueryStatus().values }
504
519
 
505
- AsyncFunction("retryOtaVersionCheck") { requireSdk().retryOtaVersionCheck().values }
506
-
507
520
  // MARK: - Version Info Commands
508
521
 
509
522
  AsyncFunction("requestVersionInfo") { requireSdk().requestVersionInfo().toMap() }
@@ -374,6 +374,11 @@ public class Bridge private constructor() {
374
374
  sendTypedMessage("photo_status", statusJson)
375
375
  }
376
376
 
377
+ @JvmStatic
378
+ fun sendCameraStatus(statusJson: Map<String, Any>) {
379
+ sendTypedMessage("camera_status", statusJson)
380
+ }
381
+
377
382
  @JvmStatic
378
383
  fun sendPhotoResponse(responseJson: Map<String, Any>) {
379
384
  sendTypedMessage("photo_response", responseJson)
@@ -587,26 +592,6 @@ public class Bridge private constructor() {
587
592
  sendTypedMessage("mtk_update_complete", eventBody as Map<String, Any>)
588
593
  }
589
594
 
590
- /**
591
- * Send OTA update available notification - glasses have detected an available update
592
- * (background mode)
593
- */
594
- @JvmStatic
595
- fun sendOtaUpdateAvailable(
596
- versionCode: Long,
597
- versionName: String,
598
- updates: List<String>,
599
- totalSize: Long
600
- ) {
601
- val eventBody = HashMap<String, Any>()
602
- eventBody["version_code"] = versionCode
603
- eventBody["version_name"] = versionName
604
- eventBody["updates"] = updates
605
- eventBody["total_size"] = totalSize
606
-
607
- sendTypedMessage("ota_update_available", eventBody as Map<String, Any>)
608
- }
609
-
610
595
  /** Send ota_start_ack — glasses confirmed receipt of ota_start command */
611
596
  @JvmStatic
612
597
  fun sendOtaStartAck() {