@mentra/bluetooth-sdk 0.1.9 → 0.1.11

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 (147) hide show
  1. package/README.md +116 -18
  2. package/android/build.gradle +289 -16
  3. package/android/gradle.properties.example +9 -0
  4. package/android/lc3Lib/build.gradle +95 -6
  5. package/android/settings.gradle +3 -0
  6. package/android/silero/LICENSE.md +17 -0
  7. package/android/silero/build.gradle +42 -0
  8. package/android/silero/proguard-rules.pro +24 -0
  9. package/android/silero/src/androidTest/assets/hello.wav +0 -0
  10. package/android/silero/src/androidTest/java/com/konovalov/vad/silero/VadSileroTest.kt +79 -0
  11. package/android/silero/src/main/assets/LICENSE +21 -0
  12. package/android/silero/src/main/assets/silero_vad.onnx +0 -0
  13. package/android/silero/src/main/java/com/konovalov/vad/silero/Vad.kt +160 -0
  14. package/android/silero/src/main/java/com/konovalov/vad/silero/VadSilero.kt +449 -0
  15. package/android/silero/src/main/java/com/konovalov/vad/silero/config/FrameSize.kt +15 -0
  16. package/android/silero/src/main/java/com/konovalov/vad/silero/config/Mode.kt +14 -0
  17. package/android/silero/src/main/java/com/konovalov/vad/silero/config/SampleRate.kt +12 -0
  18. package/android/silero/src/main/java/com/konovalov/vad/silero/utils/AudioUtils.kt +47 -0
  19. package/android/silero/src/main/java/com/konovalov/vad/silero/utils/TensorMap.kt +41 -0
  20. package/android/silero/src/test/java/com/konovalov/vad/silero/utils/AudioUtilsTest.kt +44 -0
  21. package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkDefaults.kt +6 -0
  22. package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +195 -34
  23. package/android/src/main/java/com/mentra/bluetoothsdk/Bridge.kt +103 -21
  24. package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +210 -18
  25. package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +30 -2
  26. package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +787 -85
  27. package/android/src/main/java/com/mentra/bluetoothsdk/audio/AudioModels.kt +6 -2
  28. package/android/src/main/java/com/mentra/bluetoothsdk/camera/CameraModels.kt +125 -11
  29. package/android/src/main/java/com/mentra/bluetoothsdk/controllers/ControllerManager.kt +12 -2
  30. package/android/src/main/java/com/mentra/bluetoothsdk/controllers/R1.kt +8 -2
  31. package/android/src/main/java/com/mentra/bluetoothsdk/debug/BleTraceLogger.kt +33 -17
  32. package/android/src/main/java/com/mentra/bluetoothsdk/events/BluetoothEvents.kt +101 -0
  33. package/android/src/main/java/com/mentra/bluetoothsdk/internal/MapParsing.kt +8 -0
  34. package/android/src/main/java/com/mentra/bluetoothsdk/photoreceiver/LocalPhotoReceiverRegistry.java +108 -0
  35. package/android/src/main/java/com/mentra/bluetoothsdk/photoreceiver/LocalPhotoUploadServer.kt +399 -74
  36. package/android/src/main/java/com/mentra/bluetoothsdk/photoreceiver/MentraPhotoReceiverModule.kt +100 -17
  37. package/android/src/main/java/com/mentra/bluetoothsdk/requests/DisplayRequests.kt +17 -0
  38. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G1.java +2 -2
  39. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G2.kt +1293 -758
  40. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Mach1.java +2 -2
  41. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.java +271 -66
  42. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraNex.kt +197 -149
  43. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraosBle.java +12753 -27036
  44. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/SGCManager.kt +64 -2
  45. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Simulated.kt +16 -4
  46. package/android/src/main/java/com/mentra/bluetoothsdk/status/DeviceStatus.kt +60 -3
  47. package/android/src/main/java/com/mentra/bluetoothsdk/streaming/StreamModels.kt +12 -3
  48. package/android/src/main/java/com/mentra/bluetoothsdk/stt/STTTools.kt +87 -36
  49. package/android/src/main/java/com/mentra/bluetoothsdk/stt/SherpaOnnxTranscriber.kt +13 -4
  50. package/android/src/main/java/com/mentra/bluetoothsdk/stt/VadGateSpeechPolicy.kt +229 -0
  51. package/android/src/main/java/com/mentra/bluetoothsdk/tts/TTSTools.kt +180 -0
  52. package/android/src/main/java/com/mentra/bluetoothsdk/utils/AvifExifStripper.java +573 -0
  53. package/android/src/main/java/com/mentra/bluetoothsdk/utils/BlePhotoUploadService.java +488 -50
  54. package/android/src/main/java/com/mentra/bluetoothsdk/utils/HeifExifTagReader.java +199 -0
  55. package/android/src/main/java/com/mentra/bluetoothsdk/utils/MessageChunkReassembler.java +18 -1
  56. package/android/src/main/java/com/mentra/bluetoothsdk/utils/MessageChunker.java +64 -18
  57. package/android/src/main/java/com/mentra/bluetoothsdk/utils/NexSGCUtils.kt +48 -39
  58. package/android/src/test/java/com/mentra/bluetoothsdk/camera/PhotoRequestTest.kt +23 -0
  59. package/android/src/test/java/com/mentra/bluetoothsdk/photoreceiver/LocalPhotoReceiverRegistryTest.java +75 -0
  60. package/android/src/test/java/com/mentra/bluetoothsdk/utils/AvifExifStripperTest.java +30 -0
  61. package/android/src/test/java/com/mentra/bluetoothsdk/utils/BlePhotoUploadServiceTest.java +98 -0
  62. package/android/src/test/resources/avif_with_exif.avif +0 -0
  63. package/build/BluetoothSdk.types.d.ts +253 -44
  64. package/build/BluetoothSdk.types.d.ts.map +1 -1
  65. package/build/BluetoothSdk.types.js +11 -10
  66. package/build/BluetoothSdk.types.js.map +1 -1
  67. package/build/_private/BluetoothSdkModule.d.ts +37 -23
  68. package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
  69. package/build/_private/BluetoothSdkModule.js +43 -52
  70. package/build/_private/BluetoothSdkModule.js.map +1 -1
  71. package/build/_private/photoRequestPayload.d.ts +4 -0
  72. package/build/_private/photoRequestPayload.d.ts.map +1 -0
  73. package/build/_private/photoRequestPayload.js +25 -0
  74. package/build/_private/photoRequestPayload.js.map +1 -0
  75. package/build/index.d.ts +1 -1
  76. package/build/index.d.ts.map +1 -1
  77. package/build/index.js +25 -0
  78. package/build/index.js.map +1 -1
  79. package/build/react/useMentraBluetooth.d.ts +2 -2
  80. package/build/react/useMentraBluetooth.d.ts.map +1 -1
  81. package/build/react/useMentraBluetooth.js +3 -3
  82. package/build/react/useMentraBluetooth.js.map +1 -1
  83. package/ios/BluetoothSdkModule.swift +177 -70
  84. package/ios/LocalPhotoUploadServer.swift +370 -105
  85. package/ios/MentraBluetoothSDK.podspec +3 -1
  86. package/ios/MentraPhotoReceiverModule.swift +62 -10
  87. package/ios/Packages/CoreObjC/include/PcmConverter.h +22 -0
  88. package/ios/Packages/SherpaOnnx/SherpaOnnx.swift +668 -26
  89. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/Info.plist +13 -9
  90. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64/Headers/sherpa-onnx/c-api/c-api.h +4551 -0
  91. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64/Headers/sherpa-onnx/c-api/cxx-api.h +1745 -0
  92. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64/libsherpa-onnx.a +0 -0
  93. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64_x86_64-simulator/Headers/sherpa-onnx/c-api/c-api.h +4551 -0
  94. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64_x86_64-simulator/Headers/sherpa-onnx/c-api/cxx-api.h +1745 -0
  95. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64_x86_64-simulator/libsherpa-onnx.a +0 -0
  96. package/ios/Source/BluetoothSdkDefaults.swift +6 -0
  97. package/ios/Source/Bridge.swift +68 -19
  98. package/ios/Source/DeviceManager.swift +167 -32
  99. package/ios/Source/DeviceStore.swift +24 -8
  100. package/ios/Source/MentraBluetoothSDK.swift +803 -47
  101. package/ios/Source/ObservableStore.swift +6 -0
  102. package/ios/Source/{Audio → audio}/AudioModels.swift +9 -7
  103. package/ios/Source/{Camera → camera}/CameraModels.swift +226 -16
  104. package/ios/Source/controllers/ControllerManager.swift +4 -3
  105. package/ios/Source/controllers/R1.swift +3 -3
  106. package/ios/Source/{Events → events}/BluetoothEvents.swift +183 -1
  107. package/ios/Source/{Internal → internal}/ValueParsing.swift +10 -1
  108. package/ios/Source/{Requests → requests}/DisplayRequests.swift +28 -4
  109. package/ios/Source/services/PhoneMic.swift +1 -0
  110. package/ios/Source/sgcs/G1.swift +5 -5
  111. package/ios/Source/sgcs/G2.swift +1494 -870
  112. package/ios/Source/sgcs/Mach1.swift +4 -4
  113. package/ios/Source/sgcs/MentraLive.swift +814 -321
  114. package/ios/Source/sgcs/MentraNex.swift +604 -244
  115. package/ios/Source/sgcs/SGCManager.swift +68 -5
  116. package/ios/Source/sgcs/Simulated.swift +9 -5
  117. package/ios/Source/sgcs/mentraos_ble.pb.swift +3073 -3134
  118. package/ios/Source/status/DeviceStatus.swift +974 -0
  119. package/ios/Source/{Status → status}/WifiHotspotStatus.swift +4 -4
  120. package/ios/Source/{Streaming → streaming}/StreamModels.swift +24 -9
  121. package/ios/Source/stt/STTTools.swift +27 -12
  122. package/ios/Source/stt/SherpaOnnxTranscriber.swift +31 -8
  123. package/ios/Source/tts/TTSTools.swift +164 -0
  124. package/ios/Source/utils/JSCExperiment.swift +7 -7
  125. package/ios/Source/utils/MessageChunkReassembler.swift +20 -1
  126. package/ios/Source/utils/MessageChunker.swift +78 -21
  127. package/ios/Source/utils/TarBz2Extractor.swift +53 -2
  128. package/package.json +1 -1
  129. package/plugin/build/withAndroid.js +68 -4
  130. package/src/BluetoothSdk.types.ts +344 -60
  131. package/src/_private/BluetoothSdkModule.ts +132 -102
  132. package/src/_private/photoRequestPayload.ts +28 -0
  133. package/src/index.ts +61 -15
  134. package/src/react/useMentraBluetooth.ts +6 -4
  135. package/android/.project +0 -28
  136. package/android/lc3Lib/.project +0 -28
  137. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/Headers/cargs.h +0 -162
  138. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/Headers/sherpa-onnx/c-api/c-api.h +0 -1852
  139. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/Headers/sherpa-onnx/c-api/cxx-api.h +0 -674
  140. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64/sherpa-onnx.a +0 -0
  141. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64_x86_64-simulator/sherpa-onnx.a +0 -0
  142. package/ios/Source/Status/DeviceStatus.swift +0 -471
  143. /package/ios/Source/{Connection → connection}/ScanSession.swift +0 -0
  144. /package/ios/Source/{Errors → errors}/BluetoothError.swift +0 -0
  145. /package/ios/Source/{Internal → internal}/BluetoothAvailability.swift +0 -0
  146. /package/ios/Source/{Status → status}/RuntimeState.swift +0 -0
  147. /package/ios/Source/{Types → types}/DeviceModels.swift +0 -0
@@ -18,15 +18,20 @@ public class BluetoothSdkModule: Module, MentraBluetoothSDKDelegate {
18
18
  "glasses_not_ready",
19
19
  "button_press",
20
20
  "touch_event",
21
+ "accel_event",
22
+ "CompassHeadingEvent",
23
+ "CompassCalibrationEvent",
21
24
  "head_up",
22
25
  "voice_activity_detection_status",
23
26
  "speaking_status",
24
27
  "battery_status",
25
28
  "wifi_status_change",
29
+ "wifi_scan_result",
26
30
  "hotspot_status_change",
27
31
  "hotspot_error",
28
32
  "photo_response",
29
33
  "photo_status",
34
+ "video_recording_status",
30
35
  "gallery_status",
31
36
  "compatible_glasses_search_stop",
32
37
  "heartbeat_sent",
@@ -34,6 +39,8 @@ public class BluetoothSdkModule: Module, MentraBluetoothSDKDelegate {
34
39
  "swipe_volume_status",
35
40
  "switch_status",
36
41
  "rgb_led_control_response",
42
+ "settings_ack",
43
+ "version_info",
37
44
  "pair_failure",
38
45
  "audio_pairing_needed",
39
46
  "audio_connected",
@@ -56,7 +63,8 @@ public class BluetoothSdkModule: Module, MentraBluetoothSDKDelegate {
56
63
  "send_command_to_ble",
57
64
  "receive_command_from_ble",
58
65
  "miniapp_selected",
59
- "captions_tester_incident"
66
+ "captions_tester_incident",
67
+ "extraction_progress"
60
68
  )
61
69
 
62
70
  OnCreate {
@@ -241,7 +249,7 @@ public class BluetoothSdkModule: Module, MentraBluetoothSDKDelegate {
241
249
  JSCExperiment.spawn(count: count)
242
250
  }
243
251
 
244
- Function("jscKillAll") { () -> Void in
252
+ Function("jscKillAll") { () in
245
253
  JSCExperiment.killAll()
246
254
  }
247
255
 
@@ -253,7 +261,7 @@ public class BluetoothSdkModule: Module, MentraBluetoothSDKDelegate {
253
261
  JSCExperiment.spawnAndMeasure(count: count, baselineMB: baselineMB)
254
262
  }
255
263
 
256
- Function("jscRunBenchmark") { () -> Void in
264
+ Function("jscRunBenchmark") { () in
257
265
  JSCExperiment.runBenchmark()
258
266
  }
259
267
 
@@ -268,26 +276,39 @@ public class BluetoothSdkModule: Module, MentraBluetoothSDKDelegate {
268
276
  // MARK: - WiFi Commands
269
277
 
270
278
  AsyncFunction("requestWifiScan") {
271
- await MainActor.run {
272
- self.bluetoothSdk().requestWifiScan()
273
- }
279
+ let sdk = await MainActor.run { self.bluetoothSdk() }
280
+ return try await sdk.requestWifiScan().map(\.dictionary)
274
281
  }
275
282
 
276
283
  AsyncFunction("sendWifiCredentials") { (ssid: String, password: String) in
277
- await MainActor.run {
278
- self.bluetoothSdk().sendWifiCredentials(ssid: ssid, password: password)
279
- }
284
+ let sdk = await MainActor.run { self.bluetoothSdk() }
285
+ return try await sdk.sendWifiCredentials(ssid: ssid, password: password).values
280
286
  }
281
287
 
282
288
  AsyncFunction("forgetWifiNetwork") { (ssid: String) in
283
- await MainActor.run {
284
- self.bluetoothSdk().forgetWifiNetwork(ssid: ssid)
285
- }
289
+ let sdk = await MainActor.run { self.bluetoothSdk() }
290
+ return try await sdk.forgetWifiNetwork(ssid: ssid).values
286
291
  }
287
292
 
288
293
  AsyncFunction("setHotspotState") { (enabled: Bool) in
294
+ let sdk = await MainActor.run { self.bluetoothSdk() }
295
+ return try await sdk.setHotspotState(enabled: enabled).values
296
+ }
297
+
298
+ AsyncFunction("setSystemTime") { (timestampMs: Double) in
299
+ let maxTimestamp = Double(Int64.max).nextDown
300
+ guard timestampMs.isFinite,
301
+ timestampMs >= Double(Int64.min),
302
+ timestampMs <= maxTimestamp
303
+ else {
304
+ throw BluetoothError(
305
+ code: "invalid_timestamp",
306
+ message: "setSystemTime timestampMs must be a finite Int64 millisecond timestamp."
307
+ )
308
+ }
309
+ let timestamp = Int64(timestampMs)
289
310
  await MainActor.run {
290
- self.bluetoothSdk().setHotspotState(enabled: enabled)
311
+ self.bluetoothSdk().setSystemTime(timestampMs: timestamp)
291
312
  }
292
313
  }
293
314
 
@@ -295,7 +316,7 @@ public class BluetoothSdkModule: Module, MentraBluetoothSDKDelegate {
295
316
 
296
317
  AsyncFunction("setGalleryModeEnabled") { (enabled: Bool) in
297
318
  let sdk = await MainActor.run { self.bluetoothSdk() }
298
- try await sdk.setGalleryModeEnabled(enabled)
319
+ return try await sdk.setGalleryModeEnabled(enabled).values
299
320
  }
300
321
 
301
322
  AsyncFunction("setVoiceActivityDetectionEnabled") { (enabled: Bool) in
@@ -303,10 +324,38 @@ public class BluetoothSdkModule: Module, MentraBluetoothSDKDelegate {
303
324
  try await sdk.setVoiceActivityDetectionEnabled(enabled)
304
325
  }
305
326
 
327
+ AsyncFunction("setButtonPhotoSettings") { (size: String) in
328
+ let sdk = await MainActor.run { self.bluetoothSdk() }
329
+ return try await sdk.setButtonPhotoSettings(size: ButtonPhotoSize(rawValue: size) ?? .medium).values
330
+ }
331
+
332
+ AsyncFunction("setButtonVideoRecordingSettings") { (width: Int, height: Int, fps: Int) in
333
+ let sdk = await MainActor.run { self.bluetoothSdk() }
334
+ return try await sdk.setButtonVideoRecordingSettings(width: width, height: height, fps: fps).values
335
+ }
336
+
337
+ AsyncFunction("setButtonCameraLed") { (enabled: Bool) in
338
+ let sdk = await MainActor.run { self.bluetoothSdk() }
339
+ return try await sdk.setButtonCameraLed(enabled: enabled).values
340
+ }
341
+
342
+ AsyncFunction("setButtonMaxRecordingTime") { (minutes: Int) in
343
+ let sdk = await MainActor.run { self.bluetoothSdk() }
344
+ return try await sdk.setButtonMaxRecordingTime(minutes: minutes).values
345
+ }
346
+
347
+ AsyncFunction("setCameraFov") { (fov: [String: Any]) in
348
+ let value = intValue(fov["fov"]) ?? CameraFov.defaultFov
349
+ let roiPosition = CameraRoiPosition.from(
350
+ rawValue: intValue(fov["roiPosition"]) ?? intValue(fov["roi_position"])
351
+ )
352
+ let sdk = await MainActor.run { self.bluetoothSdk() }
353
+ return try await sdk.setCameraFov(CameraFov(fov: value, roiPosition: roiPosition)).values
354
+ }
355
+
306
356
  AsyncFunction("queryGalleryStatus") {
307
- await MainActor.run {
308
- self.bluetoothSdk().queryGalleryStatus()
309
- }
357
+ let sdk = await MainActor.run { self.bluetoothSdk() }
358
+ return try await sdk.queryGalleryStatus().values
310
359
  }
311
360
 
312
361
  AsyncFunction("requestPhoto") { (params: [String: Any]) in
@@ -320,6 +369,7 @@ public class BluetoothSdkModule: Module, MentraBluetoothSDKDelegate {
320
369
  let authToken = params["authToken"] as? String ?? ""
321
370
  let compress = params["compress"] as? String ?? "none"
322
371
  let flash = params["flash"] as? Bool ?? true
372
+ let save = params["save"] as? Bool ?? params["saveToGallery"] as? Bool ?? false
323
373
  let sound = params["sound"] as? Bool ?? true
324
374
  let exposureTimeNs: Double?
325
375
  switch params["exposureTimeNs"] {
@@ -337,7 +387,8 @@ public class BluetoothSdkModule: Module, MentraBluetoothSDKDelegate {
337
387
  case let value as Int:
338
388
  iso = value > 0 ? value : nil
339
389
  case let value as Double:
340
- iso = value.isFinite && value > 0 ? Int(value) : nil
390
+ // Guard against Int(Double) trapping on out-of-range values.
391
+ iso = (value.isFinite && value > 0 && value < Double(Int.max)) ? Int(value) : nil
341
392
  case let value as NSNumber:
342
393
  let intValue = value.intValue
343
394
  iso = intValue > 0 ? intValue : nil
@@ -345,44 +396,46 @@ public class BluetoothSdkModule: Module, MentraBluetoothSDKDelegate {
345
396
  iso = nil
346
397
  }
347
398
 
348
- await MainActor.run {
349
- self.bluetoothSdk().requestPhoto(
350
- PhotoRequest(
351
- requestId: requestId,
352
- appId: appId,
353
- size: PhotoSize(rawValue: size) ?? .medium,
354
- webhookUrl: webhookUrl,
355
- authToken: authToken,
356
- compress: PhotoCompression(rawValue: compress),
357
- flash: flash,
358
- sound: sound,
359
- exposureTimeNs: exposureTimeNs,
360
- iso: iso
361
- )
399
+ let sdk = await MainActor.run { self.bluetoothSdk() }
400
+ return try await sdk.requestPhoto(
401
+ PhotoRequest(
402
+ requestId: requestId,
403
+ appId: appId,
404
+ size: PhotoSize(rawValue: size) ?? .medium,
405
+ webhookUrl: webhookUrl,
406
+ authToken: authToken,
407
+ compress: PhotoCompression(rawValue: compress),
408
+ flash: flash,
409
+ save: save,
410
+ sound: sound,
411
+ exposureTimeNs: exposureTimeNs,
412
+ iso: iso
362
413
  )
363
- }
414
+ ).values
364
415
  }
365
416
 
366
417
  // MARK: - OTA Commands
367
418
 
368
419
  AsyncFunction("sendOtaStart") {
369
- await MainActor.run {
370
- self.bluetoothSdk().sendOtaStart()
371
- }
420
+ let sdk = await MainActor.run { self.bluetoothSdk() }
421
+ return try await sdk.sendOtaStart().values
372
422
  }
373
423
 
374
424
  AsyncFunction("sendOtaQueryStatus") {
375
- await MainActor.run {
376
- self.bluetoothSdk().sendOtaQueryStatus()
377
- }
425
+ let sdk = await MainActor.run { self.bluetoothSdk() }
426
+ return try await sdk.sendOtaQueryStatus().values
427
+ }
428
+
429
+ AsyncFunction("retryOtaVersionCheck") {
430
+ let sdk = await MainActor.run { self.bluetoothSdk() }
431
+ return try await sdk.retryOtaVersionCheck().values
378
432
  }
379
433
 
380
434
  // MARK: - Version Info Commands
381
435
 
382
436
  AsyncFunction("requestVersionInfo") {
383
- await MainActor.run {
384
- self.bluetoothSdk().requestVersionInfo()
385
- }
437
+ let sdk = await MainActor.run { self.bluetoothSdk() }
438
+ return try await sdk.requestVersionInfo().dictionary
386
439
  }
387
440
 
388
441
  // MARK: - Power Control Commands
@@ -401,37 +454,43 @@ public class BluetoothSdkModule: Module, MentraBluetoothSDKDelegate {
401
454
 
402
455
  // MARK: - Video Recording Commands
403
456
 
404
- AsyncFunction("startVideoRecording") { (requestId: String, save: Bool, sound: Bool) in
405
- await MainActor.run {
406
- self.bluetoothSdk().startVideoRecording(
407
- VideoRecordingRequest(requestId: requestId, save: save, sound: sound)
408
- )
457
+ AsyncFunction("startVideoRecording") {
458
+ (requestId: String, save: Bool, sound: Bool, settings: [String: Any]?) in
459
+ // Optional per-recording {width,height,fps}. Absent fields stay 0, which
460
+ // the glasses treat as "use the saved button-video default". JS numbers
461
+ // arrive as Double across the bridge, so coerce to Int.
462
+ func dim(_ key: String) -> Int {
463
+ (settings?[key] as? NSNumber)?.intValue ?? 0
409
464
  }
465
+ let sdk = await MainActor.run { self.bluetoothSdk() }
466
+ return try await sdk.startVideoRecording(
467
+ VideoRecordingRequest(
468
+ requestId: requestId, save: save, sound: sound,
469
+ width: dim("width"), height: dim("height"), fps: dim("fps")
470
+ )
471
+ ).values
410
472
  }
411
473
 
412
474
  AsyncFunction("stopVideoRecording") { (requestId: String) in
413
- await MainActor.run {
414
- self.bluetoothSdk().stopVideoRecording(requestId: requestId)
415
- }
475
+ let sdk = await MainActor.run { self.bluetoothSdk() }
476
+ return try await sdk.stopVideoRecording(requestId: requestId).values
416
477
  }
417
478
 
418
479
  // MARK: - Stream Commands
419
480
 
420
481
  AsyncFunction("startStream") { (params: [String: Any]) in
421
- await MainActor.run {
422
- self.bluetoothSdk().startStream(StreamRequest(values: params))
423
- }
482
+ let sdk = await MainActor.run { self.bluetoothSdk() }
483
+ return try await sdk.startStream(StreamRequest(values: params)).values
424
484
  }
425
485
 
426
486
  AsyncFunction("stopStream") {
427
- await MainActor.run {
428
- self.bluetoothSdk().stopStream()
429
- }
487
+ let sdk = await MainActor.run { self.bluetoothSdk() }
488
+ return try await sdk.stopStream().values
430
489
  }
431
490
 
432
- AsyncFunction("sendCloudStreamKeepAlive") { (params: [String: Any]) in
491
+ AsyncFunction("sendExternallyManagedStreamKeepAlive") { (params: [String: Any]) in
433
492
  await MainActor.run {
434
- self.bluetoothSdk().sendCloudStreamKeepAlive(StreamKeepAliveRequest(values: params))
493
+ self.bluetoothSdk().sendExternallyManagedStreamKeepAlive(StreamKeepAliveRequest(values: params))
435
494
  }
436
495
  }
437
496
 
@@ -458,19 +517,18 @@ public class BluetoothSdkModule: Module, MentraBluetoothSDKDelegate {
458
517
  requestId: String, packageName: String?, action: String, color: String?,
459
518
  onDurationMs: Int, offDurationMs: Int, count: Int
460
519
  ) in
461
- await MainActor.run {
462
- self.bluetoothSdk().rgbLedControl(
463
- RgbLedRequest(
464
- requestId: requestId,
465
- packageName: packageName,
466
- action: RgbLedAction(rawValue: action) ?? .off,
467
- color: color.flatMap(RgbLedColor.init(rawValue:)),
468
- onDurationMs: onDurationMs,
469
- offDurationMs: offDurationMs,
470
- count: count
471
- )
520
+ let sdk = await MainActor.run { self.bluetoothSdk() }
521
+ return try await sdk.rgbLedControl(
522
+ RgbLedRequest(
523
+ requestId: requestId,
524
+ packageName: packageName,
525
+ action: RgbLedAction(rawValue: action) ?? .off,
526
+ color: color.flatMap(RgbLedColor.init(rawValue:)),
527
+ onDurationMs: onDurationMs,
528
+ offDurationMs: offDurationMs,
529
+ count: count
472
530
  )
473
- }
531
+ ).values
474
532
  }
475
533
 
476
534
  // MARK: - Microphone Commands
@@ -525,6 +583,39 @@ public class BluetoothSdkModule: Module, MentraBluetoothSDKDelegate {
525
583
  AsyncFunction("extractTarBz2") { (sourcePath: String, destinationPath: String) -> Bool in
526
584
  return STTTools.extractTarBz2(sourcePath: sourcePath, destinationPath: destinationPath)
527
585
  }
586
+
587
+ // MARK: - TTS Model Management
588
+
589
+ AsyncFunction("setTtsModelDetails") { (path: String, languageCode: String) in
590
+ TTSTools.setTtsModelDetails(path, languageCode)
591
+ }
592
+
593
+ AsyncFunction("getTtsModelPath") { () -> String in
594
+ return TTSTools.getTtsModelPath()
595
+ }
596
+
597
+ AsyncFunction("getTtsModelLanguage") { () -> String in
598
+ return TTSTools.getTtsModelLanguage()
599
+ }
600
+
601
+ AsyncFunction("checkTtsModelAvailable") { () -> Bool in
602
+ return TTSTools.checkTTSModelAvailable()
603
+ }
604
+
605
+ AsyncFunction("validateTtsModel") { (path: String) -> Bool in
606
+ return TTSTools.validateTTSModel(path)
607
+ }
608
+
609
+ AsyncFunction("generateTtsAudio") {
610
+ (text: String, modelPath: String, outputPath: String, speakerId: Int, speed: Double) -> Bool in
611
+ return TTSTools.generateTtsAudio(
612
+ text: text,
613
+ modelPath: modelPath,
614
+ outputPath: outputPath,
615
+ speakerId: speakerId,
616
+ speed: speed
617
+ )
618
+ }
528
619
  }
529
620
 
530
621
  @MainActor
@@ -610,10 +701,26 @@ public class BluetoothSdkModule: Module, MentraBluetoothSDKDelegate {
610
701
  sendEvent("photo_response", response.values)
611
702
  case let .photoStatus(status):
612
703
  sendEvent("photo_status", status.values)
704
+ case let .videoRecordingStatus(status):
705
+ sendEvent("video_recording_status", status.values)
706
+ case let .rgbLedControlResponse(response):
707
+ sendEvent("rgb_led_control_response", response.values)
613
708
  case let .streamStatus(status):
614
709
  sendEvent("stream_status", status.values)
615
710
  case let .keepAliveAck(ack):
616
711
  sendEvent("keep_alive_ack", ack.values)
712
+ case let .otaUpdateAvailable(event):
713
+ sendEvent("ota_update_available", event.values)
714
+ case let .otaStartAck(event):
715
+ sendEvent("ota_start_ack", event.values)
716
+ case let .otaStatus(event):
717
+ sendEvent("ota_status", event.values)
718
+ case let .settingsAck(event):
719
+ sendEvent("settings_ack", event.values)
720
+ case let .versionInfo(event):
721
+ var values = event.dictionary
722
+ values["type"] = "version_info"
723
+ sendEvent("version_info", values)
617
724
  case let .localTranscription(transcription):
618
725
  sendEvent("local_transcription", transcription.values)
619
726
  case let .raw(name, values):