@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
@@ -217,6 +217,57 @@ public struct StreamResolvedConfig: Equatable {
217
217
  }
218
218
  }
219
219
 
220
+ private func streamInt64Value(_ value: Any?) -> Int64? {
221
+ if let int64 = value as? Int64 { return int64 }
222
+ if let int = value as? Int { return Int64(int) }
223
+ if let number = value as? NSNumber { return number.int64Value }
224
+ return nil
225
+ }
226
+
227
+ /// Live encoder and device telemetry reported by the glasses while streaming.
228
+ public struct StreamLiveStats: Equatable {
229
+ public let bitrate: Int64?
230
+ public let fps: Double?
231
+ public let droppedFrames: Int64?
232
+ public let duration: Int64?
233
+ public let temperatureC: Double?
234
+
235
+ public init(
236
+ bitrate: Int64? = nil,
237
+ fps: Double? = nil,
238
+ droppedFrames: Int64? = nil,
239
+ duration: Int64? = nil,
240
+ temperatureC: Double? = nil
241
+ ) {
242
+ self.bitrate = bitrate
243
+ self.fps = fps
244
+ self.droppedFrames = droppedFrames
245
+ self.duration = duration
246
+ self.temperatureC = temperatureC
247
+ }
248
+
249
+ init?(values: [String: Any]?) {
250
+ guard let values else { return nil }
251
+ self.init(
252
+ bitrate: streamInt64Value(values["bitrate"]),
253
+ fps: doubleValue(values["fps"]),
254
+ droppedFrames: streamInt64Value(values["droppedFrames"]),
255
+ duration: streamInt64Value(values["duration"]),
256
+ temperatureC: doubleValue(values["temperatureC"])
257
+ )
258
+ }
259
+
260
+ var values: [String: Any] {
261
+ var values: [String: Any] = [:]
262
+ if let bitrate { values["bitrate"] = bitrate }
263
+ if let fps { values["fps"] = fps }
264
+ if let droppedFrames { values["droppedFrames"] = droppedFrames }
265
+ if let duration { values["duration"] = duration }
266
+ if let temperatureC { values["temperatureC"] = temperatureC }
267
+ return values
268
+ }
269
+ }
270
+
220
271
  public struct StreamRequest {
221
272
  public let streamUrl: String
222
273
  public let streamId: String
@@ -553,13 +604,22 @@ public enum StreamStatus: CustomStringConvertible, Equatable {
553
604
 
554
605
  public struct StreamStatusEvent: CustomStringConvertible {
555
606
  public let status: StreamStatus
556
-
557
- public init(status: StreamStatus) {
607
+ public let stats: StreamLiveStats?
608
+ /// True when the glasses will retry the failed publisher themselves
609
+ /// (emitting side lands in PR #3488); absent on older firmware and on
610
+ /// events not parsed from a glasses status map. Carried here instead of
611
+ /// as an `.error` associated value so the public case stays unchanged.
612
+ public private(set) var willRetry: Bool?
613
+
614
+ public init(status: StreamStatus, stats: StreamLiveStats? = nil) {
558
615
  self.status = status
616
+ self.stats = stats
559
617
  }
560
618
 
561
619
  public init(values: [String: Any]) {
562
620
  status = StreamStatus(values: values)
621
+ stats = StreamLiveStats(values: values["stats"] as? [String: Any])
622
+ willRetry = boolValue(values, "willRetry")
563
623
  }
564
624
 
565
625
  public var state: StreamState {
@@ -577,6 +637,12 @@ public struct StreamStatusEvent: CustomStringConvertible {
577
637
  public var values: [String: Any] {
578
638
  var values = status.values
579
639
  values["type"] = "stream_status"
640
+ if let stats {
641
+ values["stats"] = stats.values
642
+ }
643
+ if let willRetry {
644
+ values["willRetry"] = willRetry
645
+ }
580
646
  return values
581
647
  }
582
648
 
@@ -111,7 +111,7 @@ class TTSTools {
111
111
  voiceStyle: (modelPath as NSString).appendingPathComponent(kFileVoiceStyle)
112
112
  )
113
113
  var modelConfig = sherpaOnnxOfflineTtsModelConfig(
114
- numThreads: 2,
114
+ numThreads: 8,
115
115
  supertonic: supertonic
116
116
  )
117
117
  var config = sherpaOnnxOfflineTtsConfig(model: modelConfig, maxNumSentences: 1)
@@ -19,6 +19,7 @@ public enum DeviceModel: String {
19
19
  case z100
20
20
  case frame
21
21
  case nimo
22
+ case ar99
22
23
  case simulated
23
24
  case r1
24
25
 
@@ -40,6 +41,8 @@ public enum DeviceModel: String {
40
41
  DeviceTypes.FRAME
41
42
  case .nimo:
42
43
  DeviceTypes.NIMO
44
+ case .ar99:
45
+ DeviceTypes.AR99
43
46
  case .simulated:
44
47
  DeviceTypes.SIMULATED
45
48
  case .r1:
@@ -65,6 +68,8 @@ public enum DeviceModel: String {
65
68
  .frame
66
69
  case DeviceTypes.NIMO:
67
70
  .nimo
71
+ case DeviceTypes.AR99:
72
+ .ar99
68
73
  case DeviceTypes.SIMULATED:
69
74
  .simulated
70
75
  case ControllerTypes.R1:
@@ -80,6 +85,7 @@ public struct Device: Identifiable, Equatable, CustomStringConvertible {
80
85
  public let name: String
81
86
  /// CoreBluetooth identifier when available.
82
87
  public let identifier: String?
88
+ public let projectName: String?
83
89
  public let rssi: Int?
84
90
  /// Stable app-facing scan-result key. Do not parse; use typed fields instead.
85
91
  public let id: String
@@ -88,12 +94,14 @@ public struct Device: Identifiable, Equatable, CustomStringConvertible {
88
94
  model: DeviceModel,
89
95
  name: String,
90
96
  identifier: String? = nil,
97
+ projectName: String? = nil,
91
98
  rssi: Int? = nil,
92
99
  id: String? = nil
93
100
  ) {
94
101
  self.model = model
95
102
  self.name = name
96
103
  self.identifier = identifier
104
+ self.projectName = projectName
97
105
  self.rssi = rssi
98
106
  self.id = id ?? identifier.flatMap { $0.isEmpty ? nil : $0 } ?? "\(model.deviceType):\(name)"
99
107
  }
@@ -111,6 +119,9 @@ public struct Device: Identifiable, Equatable, CustomStringConvertible {
111
119
  if let identifier, !identifier.isEmpty {
112
120
  values["address"] = identifier
113
121
  }
122
+ if let projectName, !projectName.isEmpty {
123
+ values["projectName"] = projectName
124
+ }
114
125
  if let rssi {
115
126
  values["rssi"] = rssi
116
127
  }
@@ -121,11 +132,13 @@ public struct Device: Identifiable, Equatable, CustomStringConvertible {
121
132
  guard let model = stringValue(values, "model") else { return nil }
122
133
  guard let name = stringValue(values, "name") else { return nil }
123
134
  let identifier = stringValue(values, "address").flatMap { $0.isEmpty ? nil : $0 }
135
+ let projectName = stringValue(values, "projectName").flatMap { $0.isEmpty ? nil : $0 }
124
136
  let rssi = intValue(values["rssi"])
125
137
  self.init(
126
138
  model: DeviceModel.fromDeviceType(model),
127
139
  name: name,
128
140
  identifier: identifier,
141
+ projectName: projectName,
129
142
  rssi: rssi,
130
143
  id: stringValue(values, "id")
131
144
  )
@@ -19,6 +19,14 @@ enum BleJsonCompact {
19
19
  "transferMethod": "tm",
20
20
  "manual": "m",
21
21
  "reconnecting": "rc",
22
+ "stats": "st",
23
+ "width": "w",
24
+ "height": "h",
25
+ "bitrate": "br",
26
+ "fps": "f",
27
+ "droppedFrames": "df",
28
+ "duration": "du",
29
+ "temperatureC": "tc",
22
30
  ]
23
31
 
24
32
  private static let shortToLong: [String: String] = {
@@ -8,6 +8,7 @@ struct DeviceTypes {
8
8
  static let NEX = "Mentra Display"
9
9
  static let FRAME = "Brilliant Frame"
10
10
  static let NIMO = "NIMO"
11
+ static let AR99 = "AR99"
11
12
 
12
13
  static let ALL = [
13
14
  SIMULATED,
@@ -19,6 +20,7 @@ struct DeviceTypes {
19
20
  NEX,
20
21
  FRAME,
21
22
  NIMO,
23
+ AR99,
22
24
  ]
23
25
 
24
26
  /// Private init to prevent instantiation
@@ -55,6 +55,10 @@ class PhoneAudioMonitor {
55
55
  return session.isOtherAudioPlaying || ownAppAudioPlaying
56
56
  }
57
57
 
58
+ func isOwnAppAudioPlaying() -> Bool {
59
+ ownAppAudioPlaying
60
+ }
61
+
58
62
  /// Notify the monitor that our own app started/stopped playing audio
59
63
  /// Called from RN AudioPlaybackService via Bridge
60
64
  func setOwnAppAudioPlaying(_ playing: Bool) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mentra/bluetooth-sdk",
3
- "version": "0.1.20",
3
+ "version": "0.1.21-beta.1",
4
4
  "description": "SDK for communicating with smart glasses",
5
5
  "main": "build/index.js",
6
6
  "react-native": "src/index.ts",
@@ -105,7 +105,6 @@
105
105
  "access": "public",
106
106
  "registry": "https://registry.npmjs.org/"
107
107
  },
108
- "dependencies": {},
109
108
  "devDependencies": {
110
109
  "@types/node": "^25.9.3",
111
110
  "expo-module-scripts": "^55.0.2",
@@ -5,7 +5,7 @@ export type GlassesNotReadyEvent = {
5
5
  }
6
6
 
7
7
  // NOTE: unlike most events below, the native module does NOT include a `type`
8
- // field on the button_press payload it sends only {buttonId, pressType,
8
+ // field on the button_press payload 闂?it sends only {buttonId, pressType,
9
9
  // timestamp} (see BluetoothSdkModule on both iOS and Android). Consumers must
10
10
  // filter on `pressType` / the "button_press" listener name, never `event.type`.
11
11
  export type ButtonPressEvent = {
@@ -85,7 +85,7 @@ export function createDisconnectedGlassesStatus(): Partial<GlassesStatus> {
85
85
  }
86
86
  }
87
87
 
88
- /** K900 `sr_getvol` response (Mentra Live glasses media step volume 0–15). */
88
+ /** K900 `sr_getvol` response (Mentra Live glasses media step volume 0闂?5). */
89
89
  export type GlassesMediaVolumeGetResult = {
90
90
  level: number
91
91
  statusCode: number
@@ -116,6 +116,16 @@ export function isConnectedWifiStatus(status: WifiStatus): status is ConnectedWi
116
116
 
117
117
  export type WifiStatusChangeEvent = WifiStatus & {
118
118
  type: "wifi_status_change"
119
+ /**
120
+ * Glasses-reported provisioning failure reason when THIS event is the verdict of a
121
+ * failed connect attempt; absent on routine link-state updates. An attempt property,
122
+ * not a link property 闂?which is why it lives on the event, not on WifiStatus:
123
+ * "connect_timeout" arrives on a disconnected status (never associated), while
124
+ * "connected_to_other_network" arrives on a *connected* status (the attempt failed
125
+ * and the glasses ended up on / fell back to a different SSID than requested).
126
+ * Requires ASG client v40+ 闂?older glasses never send it.
127
+ */
128
+ error?: string
119
129
  }
120
130
 
121
131
  export type HotspotStatus = {state: "disabled"} | {state: "enabled"; ssid: string; password: string; localIp: string}
@@ -405,6 +415,7 @@ export type SettingsAckSetting =
405
415
  | "button_video_recording"
406
416
  | "button_max_recording_time"
407
417
  | "camera_fov"
418
+ | "camera_fov_override"
408
419
  | "camera_tuning"
409
420
 
410
421
  export type SettingsAckEvent = {
@@ -416,6 +427,7 @@ export type SettingsAckEvent = {
416
427
  fov?: number
417
428
  roiPosition?: CameraRoiPositionValue
418
429
  hardwareApplied?: boolean
430
+ leaseId?: string
419
431
  active?: boolean
420
432
  size?: ButtonPhotoSize | string
421
433
  width?: number
@@ -441,17 +453,21 @@ export type RgbLedAction = "on" | "off"
441
453
  export type RgbLedColor = "red" | "green" | "blue" | "orange" | "white"
442
454
  export type PhotoSize = "low" | "medium" | "high" | "max"
443
455
  export type PhotoMode = "photo" | "text"
456
+ export type PhotoTransferMethod = "auto" | "direct" | "ble"
444
457
  export type ButtonPhotoSize = "low" | "medium" | "high" | "max"
445
458
 
446
459
  /**
447
460
  * @deprecated Sticky action-button photo presets via {@link BluetoothSdkPublicModule.setPhotoCaptureDefaults}
448
461
  * are deprecated. Prefer per-request {@link BluetoothSdkPublicModule.requestPhoto} options
449
- * (e.g. `mode: "text"` for AE ÷3) instead of persisting button-photo tuning on the glasses.
462
+ * (e.g. `mode: "text"` for text sensor size/crop, or explicit `aeExposureDivisor`) instead of
463
+ * persisting button-photo tuning on the glasses.
450
464
  */
451
465
  export type PhotoCaptureDefaults = {
452
466
  size?: PhotoSize
453
- mfnr?: boolean
467
+ /** ZSL preview buffering for physical camera-button photos. */
454
468
  zsl?: boolean
469
+ /** MFNR still capture for physical camera-button photos. */
470
+ mfnr?: boolean
455
471
  noiseReduction?: boolean
456
472
  edgeEnhancement?: boolean
457
473
  ispDigitalGain?: number
@@ -497,6 +513,7 @@ export const DeviceModels = {
497
513
  Z100: "Vuzix Z100",
498
514
  Frame: "Brilliant Frame",
499
515
  Nimo: "NIMO",
516
+ Ar99: "AR99",
500
517
  R1: "Even Realities R1",
501
518
  } as const
502
519
 
@@ -511,7 +528,7 @@ export type DashboardMenuItem = {
511
528
 
512
529
  export const CAMERA_FOV_MIN = 62
513
530
  export const CAMERA_FOV_MAX = 118
514
- export const CAMERA_FOV_DEFAULT = 102
531
+ export const CAMERA_FOV_DEFAULT = CAMERA_FOV_MAX
515
532
 
516
533
  export type CameraRoiPosition = "center" | "bottom" | "top"
517
534
  export type CameraRoiPositionValue = 0 | 1 | 2
@@ -533,6 +550,13 @@ export type CameraFovResult = {
533
550
  timestamp: number
534
551
  }
535
552
 
553
+ export type CameraFovOverrideRequest = CameraFovRequest & {
554
+ /** Phone-owned lease used to make delayed releases safe. */
555
+ leaseId: string
556
+ /** Safety TTL; refresh the same lease/configuration to extend without a HAL restart. */
557
+ ttlMs?: number
558
+ }
559
+
536
560
  export type CameraFovSetting = {
537
561
  fov: number
538
562
  roiPosition: CameraRoiPositionValue
@@ -551,6 +575,8 @@ export type PhotoRequestParams = {
551
575
  appId?: string
552
576
  size: PhotoSize
553
577
  mode?: PhotoMode
578
+ /** `direct` disables BLE fallback; `ble` skips direct upload and forces phone-relayed transfer. */
579
+ transferMethod?: PhotoTransferMethod
554
580
  webhookUrl: string | null
555
581
  authToken: string | null
556
582
  compress: PhotoCompression
@@ -566,17 +592,26 @@ export type PhotoRequestParams = {
566
592
  /** Requested on wire; glasses may log not_implemented. */
567
593
  noiseReduction?: boolean
568
594
  edgeEnhancement?: boolean
569
- mfnr?: boolean
595
+ /** ZSL buffering. Forced off for manual/scan stills because fixed sensor controls take priority. */
570
596
  zsl?: boolean
597
+ /** MFNR still capture. Forced off for manual/scan stills because fixed sensor controls take priority. */
598
+ mfnr?: boolean
571
599
  ispDigitalGain?: number
572
600
  ispAnalogGain?: string
573
601
  }
574
602
 
575
603
  export type WarmUpCameraParams = {
604
+ /** Supply this when the owner needs to call stopCameraWarmUp during teardown. */
576
605
  requestId?: string
577
606
  size: PhotoSize
607
+ mode?: PhotoMode
578
608
  exposureTimeNs?: number | null
609
+ /** Ready-state hold; defaults to 15 seconds and is capped at 60 seconds by ASG. */
579
610
  durationMs?: number
611
+ /** ZSL preview buffering for the warm-up session. */
612
+ zsl?: boolean
613
+ /** MFNR still capture for the warm-up session. */
614
+ mfnr?: boolean
580
615
  }
581
616
 
582
617
  export type StreamVideoConfig = {
@@ -697,7 +732,7 @@ export type StreamResolvedConfig = {
697
732
  }
698
733
  }
699
734
 
700
- /** Live encoder telemetry, when the glasses emit it (no firmware does yet). */
735
+ /** Live encoder and device telemetry emitted periodically by supported glasses firmware. */
701
736
  export type StreamLiveStats = {
702
737
  /** Current encoded video bitrate in bits per second. */
703
738
  bitrate?: number
@@ -706,7 +741,7 @@ export type StreamLiveStats = {
706
741
  droppedFrames?: number
707
742
  /** Seconds since the stream started. */
708
743
  duration?: number
709
- /** Device temperature in °C, if the hardware reports it. */
744
+ /** Device temperature in 闂佺娅i悡? if the hardware reports it. */
710
745
  temperatureC?: number
711
746
  }
712
747
 
@@ -766,6 +801,17 @@ export type MtkUpdateCompleteEvent = {
766
801
  timestamp: number
767
802
  }
768
803
 
804
+ /**
805
+ * The glasses process restarted while the BES kept the BLE link alive (its `sid`
806
+ * changed, or first appeared after an update from a pre-sid build). There is no
807
+ * physical disconnect for this — treat it as the logical reconnect edge.
808
+ */
809
+ export type GlassesSessionChangedEvent = {
810
+ type: "glasses_session_changed"
811
+ previous_sid: string
812
+ sid: string
813
+ }
814
+
769
815
  /** @deprecated Glasses no longer emit ota_progress; use {@link OtaStatusEvent} and status-store mapping. */
770
816
  export type OtaProgressEvent = {
771
817
  type: "ota_progress"
@@ -782,16 +828,6 @@ export type OtaStartAckEvent = {
782
828
  type: "ota_start_ack"
783
829
  timestamp: number
784
830
  }
785
-
786
- export type OtaUpdateAvailableEvent = {
787
- type: "ota_update_available"
788
- version_code?: number
789
- version_name?: string
790
- updates?: string[]
791
- total_size?: number
792
- cache_ready?: boolean
793
- }
794
-
795
831
  export type OtaStatusEvent = {
796
832
  type: "ota_status"
797
833
  session_id: string
@@ -838,6 +874,8 @@ export type BluetoothSdkModuleEvents = {
838
874
  speaking_status: (event: SpeakingStatusEvent) => void
839
875
  battery_status: (event: BatteryStatusEvent) => void
840
876
  local_transcription: (event: LocalTranscriptionEvent) => void
877
+ phone_notification: (event: PhoneNotificationEvent) => void
878
+ phone_notification_dismissed: (event: PhoneNotificationDismissedEvent) => void
841
879
  wifi_status_change: (event: WifiStatusChangeEvent) => void
842
880
  wifi_scan_result: (event: WifiScanResultEvent) => void
843
881
  hotspot_status_change: (event: HotspotStatusChangeEvent) => void
@@ -868,9 +906,10 @@ export type BluetoothSdkModuleEvents = {
868
906
  stream_status: (event: StreamStatusEvent) => void
869
907
  keep_alive_ack: (event: KeepAliveAckEvent) => void
870
908
  mtk_update_complete: (event: MtkUpdateCompleteEvent) => void
871
- ota_update_available: (event: OtaUpdateAvailableEvent) => void
909
+ glasses_session_changed: (event: GlassesSessionChangedEvent) => void
872
910
  ota_start_ack: (event: OtaStartAckEvent) => void
873
911
  ota_status: (event: OtaStatusEvent) => void
912
+ ar99_ota_status: (event: Ar99OtaStatusEvent) => void
874
913
  version_info: (event: VersionInfoEvent) => void
875
914
  send_command_to_ble: (event: BleCommandTraceEvent) => void
876
915
  receive_command_from_ble: (event: BleCommandTraceEvent) => void
@@ -884,6 +923,33 @@ export interface ExtractionProgressEvent {
884
923
  totalBytes: number
885
924
  }
886
925
 
926
+ export interface Ar99OtaStatusEvent {
927
+ type: "ar99_ota_status"
928
+ phase: string
929
+ progress: number
930
+ offset: number
931
+ total: number
932
+ errorMessage?: string
933
+ error_message?: string
934
+ }
935
+
936
+ export interface PhoneNotificationEvent {
937
+ notificationId: string
938
+ app: string
939
+ title: string
940
+ content: string
941
+ priority: string
942
+ timestamp: number
943
+ packageName: string
944
+ }
945
+
946
+ export interface PhoneNotificationDismissedEvent {
947
+ notificationId: string
948
+ notificationKey: string
949
+ packageName: string
950
+ timestamp: number
951
+ }
952
+
887
953
  export type PublicGlassesStatus = Omit<
888
954
  GlassesStatus,
889
955
  "otaUpdateAvailable" | "otaProgress" | "otaInProgress" | "otaVersionUrl"
@@ -939,9 +1005,9 @@ export type BluetoothSdkEventMap = {
939
1005
  mic_pcm: MicPcmEvent
940
1006
  mic_lc3: MicLc3Event
941
1007
  stream_status: StreamStatusEvent
942
- ota_update_available: OtaUpdateAvailableEvent
943
1008
  ota_start_ack: OtaStartAckEvent
944
1009
  ota_status: OtaStatusEvent
1010
+ ar99_ota_status: Ar99OtaStatusEvent
945
1011
  version_info: VersionInfoEvent
946
1012
  extraction_progress: ExtractionProgressEvent
947
1013
  }
@@ -993,15 +1059,20 @@ export interface BluetoothSdkPublicModule {
993
1059
 
994
1060
  setGalleryModeEnabled(enabled: boolean): Promise<SettingsAckSuccessEvent>
995
1061
  setVoiceActivityDetectionEnabled(enabled: boolean): Promise<void>
1062
+ setLoudnessGateEnabled(enabled: boolean): Promise<void>
996
1063
  /**
997
1064
  * @deprecated Sticky action-button photo presets are deprecated. Prefer per-request
998
- * `requestPhoto(...)` options (e.g. `mode: "text"` for AE ÷3, or explicit per-shot
1065
+ * `requestPhoto(...)` options (e.g. `mode: "text"` for text sensor size/crop, or explicit per-shot
999
1066
  * fields). Still functional until removed in a future release.
1000
1067
  */
1001
1068
  setPhotoCaptureDefaults(settings: PhotoCaptureDefaults): Promise<SettingsAckSuccessEvent>
1002
1069
  setVideoRecordingDefaults(settings: VideoRecordingDefaults): Promise<SettingsAckSuccessEvent>
1003
1070
  setMaxVideoRecordingDuration(minutes: number): Promise<SettingsAckSuccessEvent>
1004
1071
  setCameraFov(request: CameraFovRequest): Promise<CameraFovResult>
1072
+ /** One-way FOV command for legacy ASG clients that do not send settings acknowledgements. */
1073
+ setLegacyCameraFov(request: CameraFovRequest): Promise<CameraFovResult>
1074
+ setCameraFovOverride(request: CameraFovOverrideRequest): Promise<CameraFovResult>
1075
+ releaseCameraFovOverride(leaseId: string): Promise<SettingsAckSuccessEvent>
1005
1076
  /**
1006
1077
  * Configure camera HAL tuning (ANR / gain) on Mentra Live glasses.
1007
1078
  *
@@ -1019,6 +1090,8 @@ export interface BluetoothSdkPublicModule {
1019
1090
  queryGalleryStatus(): Promise<GalleryStatusEvent>
1020
1091
  requestPhoto(params: PhotoRequestParams): Promise<PhotoSuccessResponseEvent>
1021
1092
  warmUpCamera(params: WarmUpCameraParams): Promise<CameraStatusEvent>
1093
+ /** Release one request-owned warm-up. Opening requests reject with camera_warm_up_cancelled. */
1094
+ stopCameraWarmUp(requestId: string): Promise<void>
1022
1095
  startVideoRecording(
1023
1096
  requestId: string,
1024
1097
  save: boolean,
@@ -1029,7 +1102,7 @@ export interface BluetoothSdkPublicModule {
1029
1102
  * Stop the active recording. When {@link webhookUrl} is provided, the glasses
1030
1103
  * upload the recorded video to it (multipart) using {@link authToken}. These
1031
1104
  * are supplied at stop time (not start) so the token is fresh when the upload
1032
- * runs a recording can last arbitrarily long. An empty/omitted webhook keeps
1105
+ * runs 闂?a recording can last arbitrarily long. An empty/omitted webhook keeps
1033
1106
  * the video on device (no upload).
1034
1107
  */
1035
1108
  stopVideoRecording(
@@ -1062,6 +1135,10 @@ export interface BluetoothSdkPublicModule {
1062
1135
  checkForOtaUpdate(): Promise<boolean>
1063
1136
  /** Start the OTA flow with the same configured manifest URL used by checkForOtaUpdate(). */
1064
1137
  startOtaUpdate(): Promise<OtaStartAckEvent>
1138
+ startAr99OtaFromFile(path: string): Promise<boolean>
1139
+ cancelAr99Ota(): Promise<void>
1140
+ sendAr99FactoryReset(): Promise<void>
1141
+ buildAr99OtaSignature(secret: string, appName: string, currentVersion: string, serialNumber: string, nonce: string): string
1065
1142
 
1066
1143
  // // stt commands (MOVE TO CRUST)
1067
1144
  // setSttModelDetails(path: string, languageCode: string): Promise<void>
@@ -1124,6 +1201,8 @@ export interface OtaUpdateInfo {
1124
1201
  updates: string[] // ["apk", "mtk", "bes"]
1125
1202
  totalSize: number
1126
1203
  cacheReady?: boolean
1204
+ /** True when the APK step installs an older build than the glasses currently run (exact-pin manifests only). */
1205
+ isDowngrade?: boolean
1127
1206
  }
1128
1207
 
1129
1208
  export interface OtaProgress {
@@ -1208,13 +1287,15 @@ export interface Device {
1208
1287
  /**
1209
1288
  * Stable app-facing key for this scan result, within the limits of the
1210
1289
  * platform identifier available to the SDK. Do not parse this value; use the
1211
- * typed model, name, address, and rssi fields instead.
1290
+ * typed model, name, address, projectName, and rssi fields instead.
1212
1291
  */
1213
1292
  id: string
1214
1293
  model: DeviceModel
1215
1294
  name: string
1216
1295
  /** Platform address/identifier when available: Android Bluetooth address, iOS CoreBluetooth identifier. */
1217
1296
  address?: string
1297
+ /** Optional AR99 project discriminator. Supported value: AR99. */
1298
+ projectName?: string
1218
1299
  /**
1219
1300
  * Optional scan signal strength. It may be undefined at first discovery and
1220
1301
  * appear in a later scan update when the platform reports RSSI metadata.
@@ -1245,7 +1326,7 @@ export interface WifiSearchResult {
1245
1326
  ssid: string
1246
1327
  requiresPassword: boolean
1247
1328
  signalStrength: number
1248
- /** Frequency in MHz (from glasses scan). 5 GHz band is typically 5170–5825. Omitted if unknown. */
1329
+ /** Frequency in MHz (from glasses scan). 5 GHz band is typically 5170闂?825. Omitted if unknown. */
1249
1330
  frequency?: number
1250
1331
  }
1251
1332
 
@@ -1298,6 +1379,7 @@ export type BluetoothSettingsUpdate = Partial<{
1298
1379
  twelve_hour_time: boolean
1299
1380
  gallery_mode: boolean
1300
1381
  voice_activity_detection_enabled: boolean
1382
+ loudness_gate_enabled: boolean
1301
1383
  button_photo_size: ButtonPhotoSize
1302
1384
  button_video_settings: {width: number; height: number; fps: number}
1303
1385
  button_video_width: number
package/src/_internal.ts CHANGED
@@ -9,3 +9,6 @@
9
9
  export {default} from "./_private/BluetoothSdkModule"
10
10
  export type {BluetoothSdkInternalModule} from "./_private/BluetoothSdkModule"
11
11
  export * from "./BluetoothSdk.types"
12
+ export {default as MentraLocalNetwork} from "./_private/MentraLocalNetworkModule"
13
+ export * from "./_private/MentraLocalNetworkModule"
14
+ export {BLUETOOTH_SDK_VERSION, sdkPinnedOtaManifestUrl} from "./_private/sdkOtaManifest"