@mentra/bluetooth-sdk 0.1.5 → 0.1.7

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 (92) hide show
  1. package/README.md +45 -27
  2. package/android/build.gradle +2 -9
  3. package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +53 -36
  4. package/android/src/main/java/com/mentra/bluetoothsdk/Bridge.kt +22 -9
  5. package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +25 -36
  6. package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +9 -4
  7. package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +68 -56
  8. package/android/src/main/java/com/mentra/bluetoothsdk/audio/AudioModels.kt +125 -0
  9. package/android/src/main/java/com/mentra/bluetoothsdk/camera/CameraModels.kt +209 -0
  10. package/android/src/main/java/com/mentra/bluetoothsdk/connection/ConnectionModels.kt +78 -0
  11. package/android/src/main/java/com/mentra/bluetoothsdk/controllers/ControllerManager.kt +4 -3
  12. package/android/src/main/java/com/mentra/bluetoothsdk/controllers/R1.kt +2 -1
  13. package/android/src/main/java/com/mentra/bluetoothsdk/events/BluetoothEvents.kt +72 -0
  14. package/android/src/main/java/com/mentra/bluetoothsdk/internal/MapParsing.kt +132 -0
  15. package/android/src/main/java/com/mentra/bluetoothsdk/requests/DisplayRequests.kt +39 -0
  16. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G1.java +1 -1
  17. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G2.kt +2 -1
  18. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Mach1.java +1 -1
  19. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.java +168 -10
  20. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraNex.kt +1 -1
  21. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/SGCManager.kt +7 -3
  22. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Simulated.kt +3 -1
  23. package/android/src/main/java/com/mentra/bluetoothsdk/status/DeviceStatus.kt +607 -0
  24. package/android/src/main/java/com/mentra/bluetoothsdk/status/RuntimeState.kt +201 -0
  25. package/android/src/main/java/com/mentra/bluetoothsdk/status/WifiHotspotStatus.kt +173 -0
  26. package/android/src/main/java/com/mentra/bluetoothsdk/streaming/StreamModels.kt +348 -0
  27. package/android/src/main/java/com/mentra/bluetoothsdk/stt/SherpaOnnxTranscriber.kt +1 -3
  28. package/android/src/main/java/com/mentra/bluetoothsdk/types/DeviceModels.kt +75 -0
  29. package/build/BluetoothSdk.types.d.ts +46 -20
  30. package/build/BluetoothSdk.types.d.ts.map +1 -1
  31. package/build/BluetoothSdk.types.js +1 -0
  32. package/build/BluetoothSdk.types.js.map +1 -1
  33. package/build/_private/BluetoothSdkModule.d.ts +6 -5
  34. package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
  35. package/build/_private/BluetoothSdkModule.js +40 -5
  36. package/build/_private/BluetoothSdkModule.js.map +1 -1
  37. package/build/index.d.ts +1 -1
  38. package/build/index.d.ts.map +1 -1
  39. package/build/index.js +4 -2
  40. package/build/index.js.map +1 -1
  41. package/build/react/useMentraBluetooth.d.ts +5 -3
  42. package/build/react/useMentraBluetooth.d.ts.map +1 -1
  43. package/build/react/useMentraBluetooth.js +15 -4
  44. package/build/react/useMentraBluetooth.js.map +1 -1
  45. package/ios/BluetoothSdkModule.swift +45 -28
  46. package/ios/MentraBluetoothSDK.podspec +0 -3
  47. package/ios/Source/Audio/AudioModels.swift +159 -0
  48. package/ios/Source/Bridge.swift +22 -11
  49. package/ios/Source/Camera/CameraModels.swift +248 -0
  50. package/ios/Source/Connection/ScanSession.swift +27 -0
  51. package/ios/Source/DeviceManager.swift +20 -97
  52. package/ios/Source/DeviceStore.swift +13 -4
  53. package/ios/Source/Errors/BluetoothError.swift +19 -0
  54. package/ios/Source/Events/BluetoothEvents.swift +149 -0
  55. package/ios/Source/Internal/BluetoothAvailability.swift +58 -0
  56. package/ios/Source/Internal/ValueParsing.swift +90 -0
  57. package/ios/Source/MentraBluetoothSDK.swift +50 -2150
  58. package/ios/Source/Requests/DisplayRequests.swift +58 -0
  59. package/ios/Source/Status/DeviceStatus.swift +471 -0
  60. package/ios/Source/Status/RuntimeState.swift +359 -0
  61. package/ios/Source/Status/WifiHotspotStatus.swift +326 -0
  62. package/ios/Source/Streaming/StreamModels.swift +436 -0
  63. package/ios/Source/Types/DeviceModels.swift +134 -0
  64. package/ios/Source/controllers/ControllerManager.swift +3 -3
  65. package/ios/Source/controllers/R1.swift +1 -1
  66. package/ios/Source/sgcs/G1.swift +1 -1
  67. package/ios/Source/sgcs/G2.swift +99 -39
  68. package/ios/Source/sgcs/Mach1.swift +1 -1
  69. package/ios/Source/sgcs/MentraLive.swift +109 -12
  70. package/ios/Source/sgcs/MentraNex.swift +1 -1
  71. package/ios/Source/sgcs/SGCManager.swift +11 -3
  72. package/ios/Source/sgcs/Simulated.swift +2 -2
  73. package/ios/Source/stt/SherpaOnnxTranscriber.swift +1 -4
  74. package/package.json +1 -1
  75. package/src/BluetoothSdk.types.ts +48 -28
  76. package/src/_private/BluetoothSdkModule.ts +47 -21
  77. package/src/index.ts +6 -4
  78. package/src/react/useMentraBluetooth.ts +19 -7
  79. package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothModels.kt +0 -1810
  80. package/android/src/main/java/com/mentra/bluetoothsdk/stt/VadGateSpeechPolicy.kt +0 -221
  81. package/ios/Packages/VAD/Common/VAD/VADQuality.swift +0 -41
  82. package/ios/Packages/VAD/Common/VAD/VADState.swift +0 -26
  83. package/ios/Packages/VAD/Common/VAD/VADStrategy.swift +0 -29
  84. package/ios/Packages/VAD/Common/VAD/VADType.swift +0 -14
  85. package/ios/Packages/VAD/Data/Configuration.swift +0 -68
  86. package/ios/Packages/VAD/Data/FrameSize.swift +0 -39
  87. package/ios/Packages/VAD/Data/Record.swift +0 -13
  88. package/ios/Packages/VAD/Data/Result.swift +0 -22
  89. package/ios/Packages/VAD/Data/SampleRate.swift +0 -48
  90. package/ios/Packages/VAD/Silero/Model/silero_vad.onnx +0 -0
  91. package/ios/Packages/VAD/Silero/SileroVAD.swift +0 -284
  92. package/ios/Packages/VAD/Silero/SileroVADStrategy.swift +0 -64
@@ -0,0 +1,348 @@
1
+ package com.mentra.bluetoothsdk
2
+
3
+ data class StreamVideoConfig @JvmOverloads constructor(
4
+ val width: Int? = null,
5
+ val height: Int? = null,
6
+ val bitrate: Int? = null,
7
+ val fps: Int? = null,
8
+ ) {
9
+ fun toMap(): Map<String, Any> =
10
+ listOfNotNull(
11
+ width?.let { "width" to it },
12
+ height?.let { "height" to it },
13
+ bitrate?.let { "bitrate" to it },
14
+ fps?.let { "frameRate" to it },
15
+ ).toMap()
16
+
17
+ companion object {
18
+ @JvmStatic
19
+ fun fromMap(values: Map<String, Any>?): StreamVideoConfig? {
20
+ values ?: return null
21
+ return StreamVideoConfig(
22
+ width = numberValue(values, "width"),
23
+ height = numberValue(values, "height"),
24
+ bitrate = numberValue(values, "bitrate"),
25
+ fps = numberValue(values, "fps"),
26
+ )
27
+ }
28
+ }
29
+ }
30
+
31
+ data class StreamAudioConfig @JvmOverloads constructor(
32
+ val bitrate: Int? = null,
33
+ val sampleRate: Int? = null,
34
+ val echoCancellation: Boolean? = null,
35
+ val noiseSuppression: Boolean? = null,
36
+ ) {
37
+ fun toMap(): Map<String, Any> =
38
+ listOfNotNull(
39
+ bitrate?.let { "bitrate" to it },
40
+ sampleRate?.let { "sampleRate" to it },
41
+ echoCancellation?.let { "echoCancellation" to it },
42
+ noiseSuppression?.let { "noiseSuppression" to it },
43
+ ).toMap()
44
+
45
+ companion object {
46
+ @JvmStatic
47
+ fun fromMap(values: Map<String, Any>?): StreamAudioConfig? {
48
+ values ?: return null
49
+ return StreamAudioConfig(
50
+ bitrate = numberValue(values, "bitrate"),
51
+ sampleRate = numberValue(values, "sampleRate"),
52
+ echoCancellation = values["echoCancellation"] as? Boolean,
53
+ noiseSuppression = values["noiseSuppression"] as? Boolean,
54
+ )
55
+ }
56
+ }
57
+ }
58
+
59
+ data class StreamRequest @JvmOverloads constructor(
60
+ val streamUrl: String,
61
+ val streamId: String = "",
62
+ val keepAlive: Boolean = true,
63
+ val keepAliveIntervalSeconds: Int = 15,
64
+ val sound: Boolean = true,
65
+ val video: StreamVideoConfig? = null,
66
+ val audio: StreamAudioConfig? = null,
67
+ val extraValues: Map<String, Any> = emptyMap(),
68
+ ) {
69
+ fun toMap(): Map<String, Any> {
70
+ val values = extraValues.toMutableMap()
71
+ values["type"] = "start_stream"
72
+ values["streamUrl"] = streamUrl
73
+ values["streamId"] = streamId
74
+ values["keepAlive"] = keepAlive
75
+ values["keepAliveIntervalSeconds"] = keepAliveIntervalSeconds
76
+ // The camera light is a privacy indicator and cannot be disabled by SDK callers.
77
+ values["flash"] = true
78
+ values["sound"] = sound
79
+ video?.toMap()?.takeIf { it.isNotEmpty() }?.let { values["video"] = it }
80
+ audio?.toMap()?.takeIf { it.isNotEmpty() }?.let { values["audio"] = it }
81
+ return values
82
+ }
83
+
84
+ companion object {
85
+ @JvmStatic
86
+ fun fromMap(values: Map<String, Any>): StreamRequest =
87
+ StreamRequest(
88
+ streamUrl =
89
+ (values["streamUrl"] ?: values["rtmpUrl"] ?: values["srtUrl"] ?: values["whipUrl"]) as? String
90
+ ?: "",
91
+ streamId = values["streamId"] as? String ?: "",
92
+ keepAlive = values["keepAlive"] as? Boolean ?: true,
93
+ keepAliveIntervalSeconds = (values["keepAliveIntervalSeconds"] as? Number)?.toInt() ?: 15,
94
+ sound = values["sound"] as? Boolean ?: true,
95
+ video = StreamVideoConfig.fromMap(stringMapValue(values["video"])),
96
+ audio = StreamAudioConfig.fromMap(stringMapValue(values["audio"])),
97
+ extraValues = values,
98
+ )
99
+ }
100
+ }
101
+
102
+ data class StreamKeepAliveRequest @JvmOverloads constructor(
103
+ val streamId: String,
104
+ val ackId: String,
105
+ val extraValues: Map<String, Any> = emptyMap(),
106
+ ) {
107
+ fun toMap(): Map<String, Any> {
108
+ val values = extraValues.toMutableMap()
109
+ values["type"] = "keep_stream_alive"
110
+ values["streamId"] = streamId
111
+ values["ackId"] = ackId
112
+ return values
113
+ }
114
+
115
+ companion object {
116
+ @JvmStatic
117
+ fun fromMap(values: Map<String, Any>): StreamKeepAliveRequest =
118
+ StreamKeepAliveRequest(
119
+ streamId = values["streamId"] as? String ?: "",
120
+ ackId = values["ackId"] as? String ?: "",
121
+ extraValues = values,
122
+ )
123
+ }
124
+ }
125
+
126
+ enum class StreamState(val value: String) {
127
+ INITIALIZING("initializing"),
128
+ STREAMING("streaming"),
129
+ STOPPING("stopping"),
130
+ STOPPED("stopped"),
131
+ RECONNECTING("reconnecting"),
132
+ RECONNECTED("reconnected"),
133
+ RECONNECT_FAILED("reconnect_failed"),
134
+ ERROR("error");
135
+
136
+ companion object {
137
+ @JvmStatic
138
+ fun fromValue(value: String?): StreamState? =
139
+ when (value?.lowercase()) {
140
+ "initializing", "starting", "connecting" -> INITIALIZING
141
+ "streaming", "streaming_started", "active" -> STREAMING
142
+ "stopping" -> STOPPING
143
+ "stopped", "not_streaming", "disconnected", "timeout" -> STOPPED
144
+ "reconnecting" -> RECONNECTING
145
+ "reconnected" -> RECONNECTED
146
+ "reconnect_failed" -> RECONNECT_FAILED
147
+ "error", "error_not_streaming" -> ERROR
148
+ else -> null
149
+ }
150
+ }
151
+ }
152
+
153
+ enum class StreamStatusKind(val value: String) {
154
+ LIFECYCLE("lifecycle"),
155
+ RECONNECT("reconnect"),
156
+ ERROR("error"),
157
+ SNAPSHOT("snapshot"),
158
+ }
159
+
160
+ sealed interface StreamStatus {
161
+ val kind: StreamStatusKind
162
+ val state: StreamState
163
+ val streamId: String?
164
+ val timestamp: Long?
165
+
166
+ fun toMap(): Map<String, Any> {
167
+ val values = mutableMapOf<String, Any>(
168
+ "kind" to kind.value,
169
+ "status" to state.value,
170
+ )
171
+ streamId?.takeIf { it.isNotBlank() }?.let { values["streamId"] = it }
172
+ timestamp?.let { values["timestamp"] = it }
173
+
174
+ when (this) {
175
+ is Lifecycle -> Unit
176
+ is Reconnecting -> {
177
+ values["attempt"] = attempt
178
+ values["maxAttempts"] = maxAttempts
179
+ values["reason"] = reason
180
+ }
181
+ is Reconnected -> values["attempt"] = attempt
182
+ is ReconnectFailed -> values["maxAttempts"] = maxAttempts
183
+ is Error -> values["errorDetails"] = errorDetails
184
+ is Snapshot -> {
185
+ values["streaming"] = streaming
186
+ values["reconnecting"] = reconnecting
187
+ attempt?.let { values["attempt"] = it }
188
+ }
189
+ }
190
+
191
+ return values
192
+ }
193
+
194
+ fun toEventMap(): Map<String, Any> = toMap() + mapOf("type" to "stream_status")
195
+
196
+ data class Lifecycle(
197
+ override val state: StreamState,
198
+ override val streamId: String?,
199
+ override val timestamp: Long?,
200
+ ) : StreamStatus {
201
+ override val kind: StreamStatusKind = StreamStatusKind.LIFECYCLE
202
+ }
203
+
204
+ data class Reconnecting(
205
+ override val streamId: String?,
206
+ val attempt: Int,
207
+ val maxAttempts: Int,
208
+ val reason: String,
209
+ override val timestamp: Long?,
210
+ ) : StreamStatus {
211
+ override val kind: StreamStatusKind = StreamStatusKind.RECONNECT
212
+ override val state: StreamState = StreamState.RECONNECTING
213
+ }
214
+
215
+ data class Reconnected(
216
+ override val streamId: String?,
217
+ val attempt: Int,
218
+ override val timestamp: Long?,
219
+ ) : StreamStatus {
220
+ override val kind: StreamStatusKind = StreamStatusKind.RECONNECT
221
+ override val state: StreamState = StreamState.RECONNECTED
222
+ }
223
+
224
+ data class ReconnectFailed(
225
+ override val streamId: String?,
226
+ val maxAttempts: Int,
227
+ override val timestamp: Long?,
228
+ ) : StreamStatus {
229
+ override val kind: StreamStatusKind = StreamStatusKind.RECONNECT
230
+ override val state: StreamState = StreamState.RECONNECT_FAILED
231
+ }
232
+
233
+ data class Error(
234
+ override val streamId: String?,
235
+ val errorDetails: String,
236
+ override val timestamp: Long?,
237
+ ) : StreamStatus {
238
+ override val kind: StreamStatusKind = StreamStatusKind.ERROR
239
+ override val state: StreamState = StreamState.ERROR
240
+ }
241
+
242
+ data class Snapshot(
243
+ override val state: StreamState,
244
+ val streaming: Boolean,
245
+ val reconnecting: Boolean,
246
+ override val streamId: String?,
247
+ val attempt: Int?,
248
+ override val timestamp: Long?,
249
+ ) : StreamStatus {
250
+ override val kind: StreamStatusKind = StreamStatusKind.SNAPSHOT
251
+ }
252
+
253
+ companion object {
254
+ @JvmStatic
255
+ fun fromMap(values: Map<String, Any>): StreamStatus {
256
+ val rawState = stringValue(values, "status")
257
+ val streaming = boolValue(values, "streaming")
258
+ val reconnecting = boolValue(values, "reconnecting") ?: false
259
+ val streamId = stringValue(values, "streamId")
260
+ val timestamp = longValue(values, "timestamp")
261
+ val attempt = numberValue(values, "attempt")
262
+ val maxAttempts = numberValue(values, "maxAttempts") ?: 0
263
+
264
+ if (streaming != null || hasAnyKey(values, "reconnecting")) {
265
+ return Snapshot(
266
+ state = when {
267
+ reconnecting -> StreamState.RECONNECTING
268
+ streaming == true -> StreamState.STREAMING
269
+ else -> StreamState.STOPPED
270
+ },
271
+ streaming = streaming == true,
272
+ reconnecting = reconnecting,
273
+ streamId = streamId,
274
+ attempt = attempt,
275
+ timestamp = timestamp,
276
+ )
277
+ }
278
+
279
+ val state = StreamState.fromValue(rawState)
280
+ ?: return Error(
281
+ streamId = streamId,
282
+ errorDetails = rawState?.let { "Unknown stream status: $it" } ?: "Missing stream status",
283
+ timestamp = timestamp,
284
+ )
285
+
286
+ return when (state) {
287
+ StreamState.RECONNECTING -> Reconnecting(
288
+ streamId = streamId,
289
+ attempt = attempt ?: 0,
290
+ maxAttempts = maxAttempts,
291
+ reason = stringValue(values, "reason") ?: "",
292
+ timestamp = timestamp,
293
+ )
294
+ StreamState.RECONNECTED -> Reconnected(
295
+ streamId = streamId,
296
+ attempt = attempt ?: 0,
297
+ timestamp = timestamp,
298
+ )
299
+ StreamState.RECONNECT_FAILED -> ReconnectFailed(
300
+ streamId = streamId,
301
+ maxAttempts = maxAttempts,
302
+ timestamp = timestamp,
303
+ )
304
+ StreamState.ERROR -> Error(
305
+ streamId = streamId,
306
+ errorDetails = stringValue(values, "errorDetails")
307
+ ?: if (rawState == "error_not_streaming") "not_streaming" else "Unknown stream error",
308
+ timestamp = timestamp,
309
+ )
310
+ else -> Lifecycle(
311
+ state = state,
312
+ streamId = streamId,
313
+ timestamp = timestamp,
314
+ )
315
+ }
316
+ }
317
+ }
318
+ }
319
+
320
+ data class StreamStatusEvent(
321
+ val status: StreamStatus,
322
+ ) {
323
+ constructor(values: Map<String, Any>) : this(StreamStatus.fromMap(values))
324
+
325
+ val state: StreamState get() = status.state
326
+ val streamId: String? get() = status.streamId
327
+ val values: Map<String, Any> get() = status.toEventMap()
328
+ }
329
+
330
+ data class KeepAliveAckEvent(
331
+ val streamId: String,
332
+ val ackId: String,
333
+ val timestamp: Long?,
334
+ ) {
335
+ constructor(values: Map<String, Any>) : this(
336
+ streamId = stringValue(values, "streamId").orEmpty(),
337
+ ackId = stringValue(values, "ackId").orEmpty(),
338
+ timestamp = longValue(values, "timestamp"),
339
+ )
340
+
341
+ val values: Map<String, Any>
342
+ get() = buildMap {
343
+ put("type", "keep_alive_ack")
344
+ put("streamId", streamId)
345
+ put("ackId", ackId)
346
+ timestamp?.let { put("timestamp", it) }
347
+ }
348
+ }
@@ -226,8 +226,7 @@ class SherpaOnnxTranscriber(private val context: Context) {
226
226
  * Feed PCM audio data (16-bit little endian) into the transcriber. This method should be called
227
227
  * continuously with short chunks (e.g., 100-300ms).
228
228
  *
229
- * Note: Audio passed to this method is assumed to have already passed VAD elsewhere, so it's
230
- * directly queued for processing without additional VAD checks.
229
+ * Audio is queued directly; microphone VAD gating is not applied in the SDK.
231
230
  */
232
231
  fun acceptAudio(pcm16le: ByteArray) {
233
232
  if (!running.get()) {
@@ -235,7 +234,6 @@ class SherpaOnnxTranscriber(private val context: Context) {
235
234
  return
236
235
  }
237
236
 
238
- // Directly queue the audio data for processing
239
237
  if (!pcmQueue.offer(pcm16le.copyOf())) {
240
238
  // Queue is full, drop oldest and try again
241
239
  pcmQueue.poll()
@@ -0,0 +1,75 @@
1
+ package com.mentra.bluetoothsdk
2
+
3
+ import com.mentra.bluetoothsdk.utils.ControllerTypes
4
+ import com.mentra.bluetoothsdk.utils.DeviceTypes
5
+
6
+ data class MentraBluetoothSdkConfig(
7
+ val deliverCallbacksOnMainThread: Boolean = true,
8
+ )
9
+
10
+ class BluetoothException(
11
+ val code: String,
12
+ message: String,
13
+ cause: Throwable? = null,
14
+ ) : IllegalStateException(message, cause)
15
+
16
+ enum class DeviceModel(val deviceType: String) {
17
+ G1(DeviceTypes.G1),
18
+ G2(DeviceTypes.G2),
19
+ MENTRA_LIVE(DeviceTypes.LIVE),
20
+ MENTRA_NEX(DeviceTypes.NEX),
21
+ MACH1(DeviceTypes.MACH1),
22
+ Z100(DeviceTypes.Z100),
23
+ FRAME(DeviceTypes.FRAME),
24
+ SIMULATED(DeviceTypes.SIMULATED),
25
+ R1(ControllerTypes.R1);
26
+
27
+ companion object {
28
+ @JvmStatic
29
+ fun fromDeviceType(deviceType: String?): DeviceModel =
30
+ values().firstOrNull { it.deviceType == deviceType } ?: MENTRA_LIVE
31
+ }
32
+ }
33
+
34
+ data class Device(
35
+ val model: DeviceModel,
36
+ val name: String,
37
+ /** Android Bluetooth address when available. */
38
+ val address: String? = null,
39
+ val rssi: Int? = null,
40
+ /** Stable app-facing scan-result key. Do not parse; use typed fields instead. */
41
+ val id: String = address?.takeIf { it.isNotBlank() } ?: "${model.deviceType}:$name",
42
+ ) {
43
+ internal fun toMap(): Map<String, Any> =
44
+ buildMap {
45
+ put("id", id)
46
+ put("model", model.deviceType)
47
+ put("name", name)
48
+ address?.takeIf { it.isNotBlank() }?.let {
49
+ put("address", it)
50
+ }
51
+ rssi?.let { put("rssi", it) }
52
+ }
53
+
54
+ companion object {
55
+ internal fun fromMap(values: Map<String, Any>): Device? {
56
+ val model = stringValue(values, "model") ?: return null
57
+ val name = stringValue(values, "name") ?: return null
58
+ val address = stringValue(values, "address")?.takeIf { it.isNotBlank() }
59
+ val rssi = numberValue(values, "rssi")
60
+ val id = stringValue(values, "id")?.takeIf { it.isNotBlank() } ?: address ?: "${model}:$name"
61
+ return Device(
62
+ model = DeviceModel.fromDeviceType(model),
63
+ name = name,
64
+ address = address,
65
+ rssi = rssi,
66
+ id = id,
67
+ )
68
+ }
69
+ }
70
+ }
71
+
72
+ data class ConnectOptions(
73
+ val saveAsDefault: Boolean = true,
74
+ val cancelExistingConnectionAttempt: Boolean = true,
75
+ )
@@ -17,9 +17,14 @@ export type TouchEvent = {
17
17
  export type HeadUpEvent = {
18
18
  up: boolean;
19
19
  };
20
- export type VadStatusEvent = {
21
- type: "vad_status";
22
- status: boolean;
20
+ export type VoiceActivityDetectionStatusEvent = {
21
+ type: "voice_activity_detection_status";
22
+ voiceActivityDetectionEnabled: boolean;
23
+ };
24
+ export type SpeakingStatusEvent = {
25
+ type: "speaking_status";
26
+ speaking: boolean;
27
+ timestamp: number;
23
28
  };
24
29
  export type BatteryStatusEvent = {
25
30
  type: "battery_status";
@@ -159,8 +164,6 @@ export type RgbLedControlResponseEvent = {
159
164
  };
160
165
  export type RgbLedAction = "on" | "off";
161
166
  export type RgbLedColor = "red" | "green" | "blue" | "orange" | "white";
162
- /** `"auto"` enables local button photo/video capture; `"manual"` reports button events without local gallery capture. */
163
- export type GalleryMode = "auto" | "manual";
164
167
  export type PhotoSize = "small" | "medium" | "large" | "full";
165
168
  export type ButtonPhotoSize = "small" | "medium" | "large";
166
169
  export type PhotoCompression = "none" | "medium" | "heavy";
@@ -187,13 +190,27 @@ export type CameraFovSetting = {
187
190
  fov: number;
188
191
  roiPosition: number;
189
192
  };
193
+ type NativeCameraFovSetting = {
194
+ fov: number;
195
+ roi_position: number;
196
+ };
190
197
  export type MicPreference = "auto" | "phone" | "glasses" | "bluetooth";
191
198
  export type MicMode = "phone" | "glasses" | "bluetoothClassic" | "bluetooth";
199
+ export type PhotoRequestParams = {
200
+ requestId: string;
201
+ appId: string;
202
+ size: PhotoSize;
203
+ webhookUrl: string | null;
204
+ authToken: string | null;
205
+ compress: PhotoCompression;
206
+ sound: boolean;
207
+ exposureTimeNs?: number | null;
208
+ };
192
209
  export type StreamVideoConfig = {
193
210
  width?: number;
194
211
  height?: number;
195
212
  bitrate?: number;
196
- frameRate?: number;
213
+ fps?: number;
197
214
  };
198
215
  export type StreamAudioConfig = {
199
216
  bitrate?: number;
@@ -251,7 +268,7 @@ export type MicPcmEvent = {
251
268
  bitsPerSample: 16;
252
269
  channels: 1;
253
270
  encoding: "pcm_s16le";
254
- vadGated: boolean;
271
+ voiceActivityDetectionEnabled: boolean;
255
272
  };
256
273
  export type MicLc3Event = {
257
274
  type: "mic_lc3";
@@ -263,7 +280,7 @@ export type MicLc3Event = {
263
280
  frameSizeBytes: number;
264
281
  bitrate: number;
265
282
  packetizedFromGlasses: boolean;
266
- vadGated: boolean;
283
+ voiceActivityDetectionEnabled: boolean;
267
284
  };
268
285
  export type StreamStatusLifecycleState = "initializing" | "streaming" | "stopping" | "stopped";
269
286
  export type StreamStatusReconnectState = "reconnecting" | "reconnected" | "reconnect_failed";
@@ -383,7 +400,8 @@ export type BluetoothSdkModuleEvents = {
383
400
  button_press: (event: ButtonPressEvent) => void;
384
401
  touch_event: (event: TouchEvent) => void;
385
402
  head_up: (event: HeadUpEvent) => void;
386
- vad_status: (event: VadStatusEvent) => void;
403
+ voice_activity_detection_status: (event: VoiceActivityDetectionStatusEvent) => void;
404
+ speaking_status: (event: SpeakingStatusEvent) => void;
387
405
  battery_status: (event: BatteryStatusEvent) => void;
388
406
  local_transcription: (event: LocalTranscriptionEvent) => void;
389
407
  wifi_status_change: (event: WifiStatusChangeEvent) => void;
@@ -417,7 +435,7 @@ export type BluetoothSdkModuleEvents = {
417
435
  miniapp_selected: (event: MiniappSelectedEvent) => void;
418
436
  };
419
437
  export type PublicGlassesStatus = Omit<GlassesStatus, "otaUpdateAvailable" | "otaProgress" | "otaInProgress" | "otaVersionUrl">;
420
- export type PublicBluetoothStatus = Pick<BluetoothStatus, "searching" | "searchingController" | "systemMicUnavailable" | "micRanking" | "currentMic" | "searchResults" | "wifiScanResults" | "lastLog" | "otherBtConnected" | "galleryModeAuto">;
438
+ export type PublicBluetoothStatus = Pick<BluetoothStatus, "searching" | "searchingController" | "systemMicUnavailable" | "micRanking" | "currentMic" | "searchResults" | "wifiScanResults" | "lastLog" | "otherBtConnected" | "galleryModeEnabled">;
421
439
  export type BluetoothSdkEventMap = {
422
440
  log: LogEvent;
423
441
  device_discovered: Device;
@@ -428,7 +446,8 @@ export type BluetoothSdkEventMap = {
428
446
  button_press: ButtonPressEvent;
429
447
  touch_event: TouchEvent;
430
448
  head_up: HeadUpEvent;
431
- vad_status: VadStatusEvent;
449
+ voice_activity_detection_status: VoiceActivityDetectionStatusEvent;
450
+ speaking_status: SpeakingStatusEvent;
432
451
  battery_status: BatteryStatusEvent;
433
452
  local_transcription: LocalTranscriptionEvent;
434
453
  wifi_status_change: WifiStatusChangeEvent;
@@ -479,20 +498,21 @@ export interface BluetoothSdkPublicModule {
479
498
  sendWifiCredentials(ssid: string, password: string): Promise<void>;
480
499
  forgetWifiNetwork(ssid: string): Promise<void>;
481
500
  setHotspotState(enabled: boolean): Promise<void>;
482
- setGalleryMode(mode: GalleryMode): Promise<void>;
501
+ setGalleryModeEnabled(enabled: boolean): Promise<void>;
502
+ setVoiceActivityDetectionEnabled(enabled: boolean): Promise<void>;
483
503
  setButtonPhotoSettings(size: ButtonPhotoSize): Promise<void>;
484
- setButtonVideoRecordingSettings(width: number, height: number, frameRate: number): Promise<void>;
504
+ setButtonVideoRecordingSettings(width: number, height: number, fps: number): Promise<void>;
485
505
  setButtonCameraLed(enabled: boolean): Promise<void>;
486
506
  setButtonMaxRecordingTime(minutes: number): Promise<void>;
487
507
  setCameraFov(fov: CameraFov): Promise<void>;
488
508
  queryGalleryStatus(): Promise<void>;
489
- requestPhoto(requestId: string, appId: string, size: PhotoSize, webhookUrl: string | null, authToken: string | null, compress: PhotoCompression, sound: boolean): Promise<void>;
509
+ requestPhoto(params: PhotoRequestParams): Promise<void>;
490
510
  startVideoRecording(requestId: string, save: boolean, sound: boolean): Promise<void>;
491
511
  stopVideoRecording(requestId: string): Promise<void>;
492
512
  startStream(params: StreamStartRequest): Promise<void>;
493
513
  stopStream(): Promise<void>;
494
514
  keepStreamAlive(params: StreamKeepAliveRequest): Promise<void>;
495
- setMicState(enabled: boolean, useGlassesMic?: boolean, bypassVad?: boolean, sendTranscript?: boolean, sendLc3Data?: boolean): Promise<void>;
515
+ setMicState(enabled: boolean, useGlassesMic?: boolean, sendTranscript?: boolean, sendLc3Data?: boolean): Promise<void>;
496
516
  setPreferredMic(preferredMic: MicPreference): Promise<void>;
497
517
  setOwnAppAudioPlaying(playing: boolean): Promise<void>;
498
518
  getGlassesMediaVolume(): Promise<GlassesMediaVolumeGetResult>;
@@ -533,6 +553,7 @@ export interface OtaProgress {
533
553
  export interface GlassesStatus {
534
554
  connection: GlassesConnectionStatus;
535
555
  micEnabled: boolean;
556
+ voiceActivityDetectionEnabled: boolean;
536
557
  bluetoothClassicConnected: boolean;
537
558
  signalStrength: number;
538
559
  /** Milliseconds since epoch when signalStrength was last refreshed by the phone BLE stack. */
@@ -588,6 +609,10 @@ export interface Device {
588
609
  name: string;
589
610
  /** Platform address/identifier when available: Android Bluetooth address, iOS CoreBluetooth identifier. */
590
611
  address?: string;
612
+ /**
613
+ * Optional scan signal strength. It may be undefined at first discovery and
614
+ * appear in a later scan update when the platform reports RSSI metadata.
615
+ */
591
616
  rssi?: number;
592
617
  }
593
618
  export interface ConnectOptions {
@@ -635,7 +660,7 @@ export interface BluetoothStatus {
635
660
  wifiScanResults: WifiSearchResult[];
636
661
  lastLog: string[];
637
662
  otherBtConnected: boolean;
638
- galleryModeAuto: boolean;
663
+ galleryModeEnabled: boolean;
639
664
  }
640
665
  export type BluetoothSettingsUpdate = Partial<{
641
666
  auth_email: string;
@@ -652,23 +677,23 @@ export type BluetoothSettingsUpdate = Partial<{
652
677
  dashboard_height: number;
653
678
  dashboard_depth: number;
654
679
  menu_apps: DashboardMenuItem[] | CoreDashboardMenuItem[] | Array<Record<string, unknown>> | null;
655
- galleryModeAuto: boolean;
680
+ gallery_mode: boolean;
681
+ voice_activity_detection_enabled: boolean;
656
682
  button_photo_size: ButtonPhotoSize;
657
683
  button_video_settings: {
658
684
  width: number;
659
685
  height: number;
660
- frameRate: number;
686
+ fps: number;
661
687
  };
662
688
  button_video_width: number;
663
689
  button_video_height: number;
664
690
  button_video_fps: number;
665
691
  button_camera_led: boolean;
666
692
  button_max_recording_time: number;
667
- camera_fov: CameraFovSetting;
693
+ camera_fov: NativeCameraFovSetting;
668
694
  should_send_pcm: boolean;
669
695
  should_send_lc3: boolean;
670
696
  should_send_transcript: boolean;
671
- bypass_vad: boolean;
672
697
  offline_mode: boolean;
673
698
  offline_captions_running: boolean;
674
699
  local_stt_fallback_active: boolean;
@@ -681,4 +706,5 @@ export type BluetoothSettingsUpdate = Partial<{
681
706
  controller_device_name: string;
682
707
  controller_address: string;
683
708
  }>;
709
+ export {};
684
710
  //# sourceMappingURL=BluetoothSdk.types.d.ts.map