@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,248 @@
1
+ import Foundation
2
+
3
+ public enum PhotoSize: String {
4
+ case small
5
+ case medium
6
+ case large
7
+ case full
8
+ }
9
+
10
+ public enum ButtonPhotoSize: String {
11
+ case small
12
+ case medium
13
+ case large
14
+ }
15
+
16
+ public enum PhotoCompression: String {
17
+ case none
18
+ case medium
19
+ case heavy
20
+ }
21
+
22
+ public struct ButtonPhotoSettings {
23
+ public let size: ButtonPhotoSize
24
+
25
+ public init(size: ButtonPhotoSize) {
26
+ self.size = size
27
+ }
28
+ }
29
+
30
+ public struct ButtonVideoRecordingSettings {
31
+ public let width: Int
32
+ public let height: Int
33
+ public let fps: Int
34
+
35
+ public init(width: Int, height: Int, fps: Int) {
36
+ self.width = width
37
+ self.height = height
38
+ self.fps = fps
39
+ }
40
+ }
41
+
42
+ public enum CameraFov {
43
+ case standard
44
+ case wide
45
+
46
+ var value: [String: Int] {
47
+ switch self {
48
+ case .standard:
49
+ ["fov": 118, "roi_position": 0]
50
+ case .wide:
51
+ ["fov": 118, "roi_position": 0]
52
+ }
53
+ }
54
+ }
55
+
56
+ public struct PhotoRequest {
57
+ public let requestId: String
58
+ public let appId: String
59
+ public let size: PhotoSize
60
+ public let webhookUrl: String?
61
+ public let authToken: String?
62
+ public let compress: PhotoCompression?
63
+ public let flash: Bool
64
+ public let sound: Bool
65
+ /// Sensor exposure time for this capture only (ns), or nil for auto exposure
66
+ public let exposureTimeNs: Double?
67
+
68
+ public init(
69
+ requestId: String,
70
+ appId: String,
71
+ size: PhotoSize,
72
+ webhookUrl: String? = nil,
73
+ authToken: String? = nil,
74
+ compress: PhotoCompression? = nil,
75
+ flash: Bool = true,
76
+ sound: Bool,
77
+ exposureTimeNs: Double? = nil
78
+ ) {
79
+ self.requestId = requestId
80
+ self.appId = appId
81
+ self.size = size
82
+ self.webhookUrl = webhookUrl
83
+ self.authToken = authToken
84
+ self.compress = compress
85
+ self.flash = flash
86
+ self.sound = sound
87
+ self.exposureTimeNs = exposureTimeNs
88
+ }
89
+ }
90
+
91
+ public enum RgbLedAction: String {
92
+ case on
93
+ case off
94
+ }
95
+
96
+ public enum RgbLedColor: String {
97
+ case red
98
+ case green
99
+ case blue
100
+ case orange
101
+ case white
102
+ }
103
+
104
+ public struct RgbLedRequest {
105
+ public let requestId: String
106
+ public let packageName: String?
107
+ public let action: RgbLedAction
108
+ public let color: RgbLedColor?
109
+ public let onDurationMs: Int
110
+ public let offDurationMs: Int
111
+ public let count: Int
112
+
113
+ public init(
114
+ requestId: String,
115
+ packageName: String?,
116
+ action: RgbLedAction,
117
+ color: RgbLedColor?,
118
+ onDurationMs: Int,
119
+ offDurationMs: Int,
120
+ count: Int
121
+ ) {
122
+ self.requestId = requestId
123
+ self.packageName = packageName
124
+ self.action = action
125
+ self.color = color
126
+ self.onDurationMs = onDurationMs
127
+ self.offDurationMs = offDurationMs
128
+ self.count = count
129
+ }
130
+ }
131
+
132
+ public struct VideoRecordingRequest {
133
+ public let requestId: String
134
+ public let save: Bool
135
+ public let sound: Bool
136
+
137
+ public init(requestId: String, save: Bool, sound: Bool) {
138
+ self.requestId = requestId
139
+ self.save = save
140
+ self.sound = sound
141
+ }
142
+ }
143
+
144
+ public enum PhotoResponse: CustomStringConvertible, Equatable {
145
+ public enum State: String {
146
+ case success
147
+ case error
148
+ }
149
+
150
+ case success(requestId: String, uploadUrl: String, timestamp: Int)
151
+ case error(requestId: String, errorCode: String?, errorMessage: String, timestamp: Int)
152
+
153
+ public init(values: [String: Any]) {
154
+ let requestId = stringValue(values, "requestId") ?? ""
155
+ let timestamp = intValue(values["timestamp"]) ?? Int(Date().timeIntervalSince1970 * 1000)
156
+ let state = stringValue(values, "state")?.lowercased()
157
+ if state == State.success.rawValue {
158
+ self = .success(
159
+ requestId: requestId,
160
+ uploadUrl: stringValue(values, "uploadUrl") ?? "",
161
+ timestamp: timestamp
162
+ )
163
+ } else {
164
+ self = .error(
165
+ requestId: requestId,
166
+ errorCode: stringValue(values, "errorCode"),
167
+ errorMessage: stringValue(values, "errorMessage") ?? "Unknown photo error",
168
+ timestamp: timestamp
169
+ )
170
+ }
171
+ }
172
+
173
+ public var state: State {
174
+ switch self {
175
+ case .success:
176
+ .success
177
+ case .error:
178
+ .error
179
+ }
180
+ }
181
+
182
+ public var requestId: String {
183
+ switch self {
184
+ case let .success(requestId, _, _), let .error(requestId, _, _, _):
185
+ requestId
186
+ }
187
+ }
188
+
189
+ public var timestamp: Int {
190
+ switch self {
191
+ case let .success(_, _, timestamp), let .error(_, _, _, timestamp):
192
+ timestamp
193
+ }
194
+ }
195
+
196
+ public var values: [String: Any] {
197
+ switch self {
198
+ case let .success(requestId, uploadUrl, timestamp):
199
+ return [
200
+ "state": State.success.rawValue,
201
+ "requestId": requestId,
202
+ "uploadUrl": uploadUrl,
203
+ "timestamp": timestamp,
204
+ ]
205
+ case let .error(requestId, errorCode, errorMessage, timestamp):
206
+ var values: [String: Any] = [
207
+ "state": State.error.rawValue,
208
+ "requestId": requestId,
209
+ "errorMessage": errorMessage,
210
+ "timestamp": timestamp,
211
+ ]
212
+ if let errorCode, !errorCode.isEmpty {
213
+ values["errorCode"] = errorCode
214
+ }
215
+ return values
216
+ }
217
+ }
218
+
219
+ public var description: String {
220
+ "PhotoResponse(requestId: \(requestId), state: \(state.rawValue))"
221
+ }
222
+ }
223
+
224
+ public struct PhotoResponseEvent: CustomStringConvertible {
225
+ public let response: PhotoResponse
226
+
227
+ public init(response: PhotoResponse) {
228
+ self.response = response
229
+ }
230
+
231
+ public init(values: [String: Any]) {
232
+ self.response = PhotoResponse(values: values)
233
+ }
234
+
235
+ public var requestId: String {
236
+ response.requestId
237
+ }
238
+
239
+ public var values: [String: Any] {
240
+ var values = response.values
241
+ values["type"] = "photo_response"
242
+ return values
243
+ }
244
+
245
+ public var description: String {
246
+ "PhotoResponseEvent(requestId: \(requestId), state: \(response.state.rawValue))"
247
+ }
248
+ }
@@ -0,0 +1,27 @@
1
+ import Foundation
2
+
3
+ public enum ScanStopReason {
4
+ case completed
5
+ case cancelled
6
+ case error
7
+ }
8
+
9
+ @MainActor
10
+ public final class ScanSession {
11
+ private let stopAction: () -> Void
12
+ private var stopped = false
13
+
14
+ init(stopAction: @escaping () -> Void) {
15
+ self.stopAction = stopAction
16
+ }
17
+
18
+ public func stop() {
19
+ guard !stopped else { return }
20
+ stopped = true
21
+ stopAction()
22
+ }
23
+
24
+ func markStopped() {
25
+ stopped = true
26
+ }
27
+ }
@@ -167,11 +167,6 @@ struct ViewState {
167
167
  set { DeviceStore.shared.apply("bluetooth", "sensing_enabled", newValue) }
168
168
  }
169
169
 
170
- private var bypassVad: Bool {
171
- get { DeviceStore.shared.get("bluetooth", "bypass_vad") as? Bool ?? true }
172
- set { DeviceStore.shared.apply("bluetooth", "bypass_vad", newValue) }
173
- }
174
-
175
170
  private var offlineCaptionsRunning: Bool {
176
171
  get { DeviceStore.shared.get("bluetooth", "offline_captions_running") as? Bool ?? false }
177
172
  set { DeviceStore.shared.apply("bluetooth", "offline_captions_running", newValue) }
@@ -285,11 +280,6 @@ struct ViewState {
285
280
  private var lastLc3Event: Date?
286
281
  private var micReinitTimer: Timer?
287
282
 
288
- // VAD:
289
- private var vad: SileroVADStrategy?
290
- private var vadBuffer = [Data]()
291
- private var isSpeaking = false
292
-
293
283
  /// STT:
294
284
  private var transcriber: SherpaOnnxTranscriber?
295
285
 
@@ -314,7 +304,6 @@ struct ViewState {
314
304
 
315
305
  override init() {
316
306
  Bridge.log("MAN: init()")
317
- vad = SileroVADStrategy()
318
307
  super.init()
319
308
 
320
309
  // Start memory monitoring (logs every 30s to help detect leaks)
@@ -336,16 +325,6 @@ struct ViewState {
336
325
  Bridge.log("SherpaOnnxTranscriber fully initialized")
337
326
  }
338
327
 
339
- Task {
340
- self.vad?.setup(
341
- sampleRate: .rate_16k,
342
- frameSize: .size_1024,
343
- quality: .normal,
344
- silenceTriggerDurationMs: 4000,
345
- speechTriggerDurationMs: 50
346
- )
347
- }
348
-
349
328
  // Initialize persistent LC3 converter for unified audio encoding
350
329
  lc3Converter = PcmConverter()
351
330
  Bridge.log("LC3 converter initialized for unified audio encoding")
@@ -362,13 +341,6 @@ struct ViewState {
362
341
 
363
342
  // MARK: - AUX Voice Data Handling
364
343
 
365
- private func checkSetVadStatus(speaking: Bool) {
366
- if speaking != isSpeaking {
367
- isSpeaking = speaking
368
- Bridge.sendVadEvent(isSpeaking)
369
- }
370
- }
371
-
372
344
  private func convertAndSendMicLc3(_ pcmData: Data) {
373
345
  guard let lc3Converter = lc3Converter else {
374
346
  Bridge.log("MAN: ERROR - LC3 converter not initialized but format is LC3")
@@ -393,23 +365,6 @@ struct ViewState {
393
365
  }
394
366
  }
395
367
 
396
- private func emptyVadBuffer() {
397
- // go through the buffer, popping from the first element in the array (FIFO):
398
- while !vadBuffer.isEmpty {
399
- let chunk = vadBuffer.removeFirst()
400
- handleSendingPcm(chunk)
401
- }
402
- }
403
-
404
- private func addToVadBuffer(_ chunk: Data) {
405
- let MAX_BUFFER_SIZE = 20
406
- vadBuffer.append(chunk)
407
- while vadBuffer.count > MAX_BUFFER_SIZE {
408
- // pop from the front of the array:
409
- vadBuffer.removeFirst()
410
- }
411
- }
412
-
413
368
  /**
414
369
  * Handle raw LC3 audio data from glasses.
415
370
  * Decodes the glasses LC3 to PCM, then forwards to handlePcm for processing.
@@ -432,59 +387,16 @@ struct ViewState {
432
387
  Bridge.log("MAN: Failed to decode glasses LC3 audio")
433
388
  return
434
389
  }
435
- // Forward to handlePcm which handles VAD and encoding
390
+ // Forward to handlePcm which handles SDK audio events and encoding.
436
391
  handlePcm(pcmData)
437
392
  }
438
393
 
439
394
  func handlePcm(_ pcmData: Data) {
440
- // handle incoming PCM data from the microphone manager and feed to the VAD:
441
- if bypassVad {
442
- handleSendingPcm(pcmData)
395
+ handleSendingPcm(pcmData)
443
396
 
444
- // Send PCM to local transcriber (always needs raw PCM)
445
- if shouldSendTranscript || offlineCaptionsRunning || localSttFallbackActive {
446
- transcriber?.acceptAudio(pcm16le: pcmData)
447
- }
448
- return
449
- }
450
-
451
- // feed PCM to the VAD:
452
- guard let vad = vad else {
453
- Bridge.log("VAD not initialized")
454
- return
455
- }
456
-
457
- // convert audioData to Int16 array for VAD:
458
- let pcmDataArray = pcmData.withUnsafeBytes { pointer -> [Int16] in
459
- Array(
460
- UnsafeBufferPointer(
461
- start: pointer.bindMemory(to: Int16.self).baseAddress,
462
- count: pointer.count / MemoryLayout<Int16>.stride
463
- )
464
- )
465
- }
466
-
467
- vad.checkVAD(pcm: pcmDataArray) { [weak self] state in
468
- guard let self = self else { return }
469
- Bridge.log("VAD State: \(state)")
470
- }
471
-
472
- let vadState = vad.currentState()
473
- if vadState == .speeching {
474
- checkSetVadStatus(speaking: true)
475
- // first send out whatever's in the vadBuffer (if there is anything):
476
- emptyVadBuffer()
477
-
478
- handleSendingPcm(pcmData)
479
-
480
- // Send PCM to local transcriber (always needs raw PCM)
481
- if shouldSendTranscript || offlineCaptionsRunning || localSttFallbackActive {
482
- transcriber?.acceptAudio(pcm16le: pcmData)
483
- }
484
- } else {
485
- checkSetVadStatus(speaking: false)
486
- // add to the vadBuffer (stores PCM for potential later sending):
487
- addToVadBuffer(pcmData)
397
+ // Send PCM to local transcriber.
398
+ if shouldSendTranscript || offlineCaptionsRunning || localSttFallbackActive {
399
+ transcriber?.acceptAudio(pcm16le: pcmData)
488
400
  }
489
401
  }
490
402
 
@@ -1013,6 +925,8 @@ struct ViewState {
1013
925
 
1014
926
  func handleDeviceDisconnected() {
1015
927
  Bridge.log("MAN: Device disconnected")
928
+ DeviceStore.shared.apply("glasses", "headUp", false)
929
+ DeviceStore.shared.apply("glasses", "voiceActivityDetectionEnabled", true)
1016
930
  // shouldSendBootingMessage = true // Reset for next first connect
1017
931
  }
1018
932
 
@@ -1280,14 +1194,22 @@ struct ViewState {
1280
1194
  _ webhookUrl: String?,
1281
1195
  _ authToken: String?,
1282
1196
  _ compress: String?,
1283
- _ sound: Bool
1197
+ _ flash: Bool,
1198
+ _ sound: Bool,
1199
+ exposureTimeNs: Double? = nil
1284
1200
  ) {
1285
1201
  Bridge.log(
1286
- "MAN: requestPhoto: \(requestId), \(appId), \(webhookUrl), size=\(size), compress=\(compress ?? "none"), flash=true, sound=\(sound)"
1202
+ "MAN: PHOTO PIPELINE [4/6] DeviceManager.requestPhoto requestId=\(requestId) appId=\(appId) webhookUrl=\(webhookUrl ?? "nil") size=\(size) compress=\(compress ?? "none") flash=\(flash) sound=\(sound) exposureTimeNs=\(exposureTimeNs.map { String($0) } ?? "nil") sgc=\(sgc != nil ? String(describing: type(of: sgc!)) : "null")"
1287
1203
  )
1288
- sgc?.requestPhoto(
1204
+ guard let sgc else {
1205
+ Bridge.log(
1206
+ "MAN: PHOTO PIPELINE — sgc is null (glasses not connected); dropping requestId=\(requestId)"
1207
+ )
1208
+ return
1209
+ }
1210
+ sgc.requestPhoto(
1289
1211
  requestId, appId: appId, size: size, webhookUrl: webhookUrl, authToken: authToken,
1290
- compress: compress, flash: true, sound: sound
1212
+ compress: compress, flash: flash, sound: sound, exposureTimeNs: exposureTimeNs
1291
1213
  )
1292
1214
  }
1293
1215
 
@@ -1393,6 +1315,7 @@ struct ViewState {
1393
1315
  DeviceStore.shared.apply("glasses", "deviceModel", "")
1394
1316
  DeviceStore.shared.apply("glasses", "fullyBooted", false)
1395
1317
  DeviceStore.shared.apply("glasses", "connected", false)
1318
+ DeviceStore.shared.apply("glasses", "voiceActivityDetectionEnabled", true)
1396
1319
  // disconnect the controller as well:
1397
1320
  searchingController = false
1398
1321
  DeviceStore.shared.apply("glasses", "controllerConnected", false)
@@ -28,6 +28,7 @@ class DeviceStore {
28
28
  store.set("glasses", "deviceModel", "")
29
29
  store.set("glasses", "firmwareVersion", "")
30
30
  store.set("glasses", "micEnabled", false)
31
+ store.set("glasses", "voiceActivityDetectionEnabled", true)
31
32
  store.set("glasses", "bluetoothClassicConnected", false)
32
33
  store.set("glasses", "caseRemoved", true)
33
34
  store.set("glasses", "caseOpen", true)
@@ -71,21 +72,26 @@ class DeviceStore {
71
72
  store.set("bluetooth", "pending_wearable", "")
72
73
  store.set("bluetooth", "device_name", "")
73
74
  store.set("bluetooth", "device_address", "")
75
+ store.set("bluetooth", "default_controller", "")
76
+ store.set("bluetooth", "pending_controller", "")
77
+ store.set("bluetooth", "controller_device_name", "")
74
78
  store.set("bluetooth", "screen_disabled", false)
75
79
  store.set("bluetooth", "preferred_mic", "auto")
76
80
  store.set("bluetooth", "sensing_enabled", true)
81
+ store.set("bluetooth", "power_saving_mode", false)
77
82
  store.set("bluetooth", "brightness", 50)
78
83
  store.set("bluetooth", "auto_brightness", true)
79
84
  store.set("bluetooth", "dashboard_height", 4)
80
85
  store.set("bluetooth", "dashboard_depth", 2)
81
86
  store.set("bluetooth", "head_up_angle", 30)
82
87
  store.set("bluetooth", "contextual_dashboard", true)
83
- store.set("bluetooth", "galleryModeAuto", true)
88
+ store.set("bluetooth", "gallery_mode", true)
89
+ store.set("bluetooth", "voice_activity_detection_enabled", true)
84
90
  store.set("bluetooth", "screen_disabled", false)
85
91
  store.set("bluetooth", "button_photo_size", "medium")
86
92
  store.set("bluetooth", "button_camera_led", true)
87
93
  store.set("bluetooth", "button_max_recording_time", 10)
88
- store.set("bluetooth", "camera_fov", ["fov": 118, "roiPosition": 0])
94
+ store.set("bluetooth", "camera_fov", ["fov": 118, "roi_position": 0])
89
95
  store.set("bluetooth", "button_video_width", 1280)
90
96
  store.set("bluetooth", "button_video_height", 720)
91
97
  store.set("bluetooth", "button_video_fps", 30)
@@ -96,7 +102,7 @@ class DeviceStore {
96
102
  store.set("bluetooth", "should_send_pcm", false)
97
103
  store.set("bluetooth", "should_send_lc3", false)
98
104
  store.set("bluetooth", "should_send_transcript", false)
99
- store.set("bluetooth", "bypass_vad", true)
105
+ store.set("bluetooth", "use_native_dashboard", false)
100
106
  }
101
107
 
102
108
  func get(_ category: String, _ key: String) -> Any? {
@@ -214,9 +220,12 @@ class DeviceStore {
214
220
  DeviceManager.shared.sgc?.setDashboardMenu(items)
215
221
  }
216
222
 
217
- case ("bluetooth", "galleryModeAuto"):
223
+ case ("bluetooth", "gallery_mode"):
218
224
  DeviceManager.shared.sgc?.sendGalleryMode()
219
225
 
226
+ case ("bluetooth", "voice_activity_detection_enabled"):
227
+ DeviceManager.shared.sgc?.sendVoiceActivityDetectionSetting()
228
+
220
229
  case ("bluetooth", "screen_disabled"):
221
230
  if let disabled = value as? Bool {
222
231
  if disabled {
@@ -0,0 +1,19 @@
1
+ import Foundation
2
+
3
+ public struct BluetoothError: Error, LocalizedError, CustomStringConvertible {
4
+ public let code: String
5
+ public let message: String
6
+
7
+ public init(code: String, message: String) {
8
+ self.code = code
9
+ self.message = message
10
+ }
11
+
12
+ public var errorDescription: String? {
13
+ message
14
+ }
15
+
16
+ public var description: String {
17
+ "\(code): \(message)"
18
+ }
19
+ }
@@ -0,0 +1,149 @@
1
+ import Foundation
2
+
3
+ public struct ButtonPressEvent: CustomStringConvertible {
4
+ public let buttonId: String
5
+ public let pressType: String
6
+ public let timestamp: Int?
7
+
8
+ public init(buttonId: String, pressType: String, timestamp: Int? = nil) {
9
+ self.buttonId = buttonId
10
+ self.pressType = pressType
11
+ self.timestamp = timestamp
12
+ }
13
+
14
+ public var description: String {
15
+ "ButtonPressEvent(buttonId: \(buttonId), pressType: \(pressType))"
16
+ }
17
+ }
18
+
19
+ public struct TouchEvent: CustomStringConvertible {
20
+ public let values: [String: Any]
21
+
22
+ public init(values: [String: Any]) {
23
+ self.values = values
24
+ }
25
+
26
+ public var deviceModel: String? {
27
+ stringValue(values, "deviceModel")
28
+ }
29
+
30
+ public var gestureName: String? {
31
+ stringValue(values, "gestureName")
32
+ }
33
+
34
+ public var timestamp: Int? {
35
+ intValue(values["timestamp"])
36
+ }
37
+
38
+ public var isSwipe: Bool {
39
+ gestureName?.localizedCaseInsensitiveContains("swipe") == true
40
+ }
41
+
42
+ public var description: String {
43
+ "TouchEvent(gestureName: \(gestureName ?? "unknown"))"
44
+ }
45
+ }
46
+
47
+ public struct VoiceActivityDetectionStatusEvent: CustomStringConvertible {
48
+ public let voiceActivityDetectionEnabled: Bool
49
+ public let values: [String: Any]
50
+
51
+ public init(values: [String: Any]) {
52
+ voiceActivityDetectionEnabled = boolValue(values, "voiceActivityDetectionEnabled") ?? true
53
+ self.values = values
54
+ }
55
+
56
+ public var description: String {
57
+ "VoiceActivityDetectionStatusEvent(voiceActivityDetectionEnabled: \(voiceActivityDetectionEnabled))"
58
+ }
59
+ }
60
+
61
+ public struct SpeakingStatusEvent: CustomStringConvertible {
62
+ public let speaking: Bool
63
+ public let values: [String: Any]
64
+
65
+ public init(values: [String: Any]) {
66
+ speaking = boolValue(values, "speaking") ?? false
67
+ self.values = values
68
+ }
69
+
70
+ public var description: String {
71
+ "SpeakingStatusEvent(speaking: \(speaking))"
72
+ }
73
+ }
74
+
75
+ public enum BluetoothEvent: CustomStringConvertible {
76
+ case buttonPress(ButtonPressEvent)
77
+ case touch(TouchEvent)
78
+ case voiceActivityDetectionStatus(VoiceActivityDetectionStatusEvent)
79
+ case speakingStatus(SpeakingStatusEvent)
80
+ case wifiStatus(WifiStatusEvent)
81
+ case hotspotStatus(HotspotStatusEvent)
82
+ case hotspotError(HotspotErrorEvent)
83
+ case photoResponse(PhotoResponseEvent)
84
+ case streamStatus(StreamStatusEvent)
85
+ case keepAliveAck(KeepAliveAckEvent)
86
+ case localTranscription(LocalTranscriptionEvent)
87
+ case raw(name: String, values: [String: Any])
88
+
89
+ public var description: String {
90
+ switch self {
91
+ case let .buttonPress(event):
92
+ event.description
93
+ case let .touch(event):
94
+ event.description
95
+ case let .voiceActivityDetectionStatus(event):
96
+ event.description
97
+ case let .speakingStatus(event):
98
+ event.description
99
+ case let .wifiStatus(event):
100
+ event.description
101
+ case let .hotspotStatus(event):
102
+ event.description
103
+ case let .hotspotError(event):
104
+ event.description
105
+ case let .photoResponse(event):
106
+ event.description
107
+ case let .streamStatus(event):
108
+ event.description
109
+ case let .keepAliveAck(event):
110
+ event.description
111
+ case let .localTranscription(event):
112
+ event.description
113
+ case let .raw(name, values):
114
+ "\(name): \(values)"
115
+ }
116
+ }
117
+ }
118
+
119
+ @MainActor
120
+ public protocol MentraBluetoothSDKDelegate: AnyObject {
121
+ func mentraBluetoothSDK(_ sdk: MentraBluetoothSDK, didUpdate state: MentraBluetoothState)
122
+ func mentraBluetoothSDK(_ sdk: MentraBluetoothSDK, didUpdateGlasses glasses: GlassesRuntimeState)
123
+ func mentraBluetoothSDK(_ sdk: MentraBluetoothSDK, didUpdateSdkState sdkState: PhoneSdkRuntimeState)
124
+ func mentraBluetoothSDK(_ sdk: MentraBluetoothSDK, didUpdateScan scan: BluetoothScanState)
125
+ func mentraBluetoothSDK(_ sdk: MentraBluetoothSDK, didDiscover device: Device)
126
+ func mentraBluetoothSDK(_ sdk: MentraBluetoothSDK, didStopScan reason: ScanStopReason)
127
+ func mentraBluetoothSDK(_ sdk: MentraBluetoothSDK, didReceive event: BluetoothEvent)
128
+ func mentraBluetoothSDK(_ sdk: MentraBluetoothSDK, didReceiveMicPcm event: MicPcmEvent)
129
+ func mentraBluetoothSDK(_ sdk: MentraBluetoothSDK, didReceiveMicLc3 event: MicLc3Event)
130
+ func mentraBluetoothSDK(_ sdk: MentraBluetoothSDK, didChangeDefaultDevice device: Device?)
131
+ func mentraBluetoothSDK(_ sdk: MentraBluetoothSDK, didLog message: String)
132
+ func mentraBluetoothSDK(_ sdk: MentraBluetoothSDK, didFail error: BluetoothError)
133
+ }
134
+
135
+ @MainActor
136
+ public extension MentraBluetoothSDKDelegate {
137
+ func mentraBluetoothSDK(_: MentraBluetoothSDK, didUpdate _: MentraBluetoothState) {}
138
+ func mentraBluetoothSDK(_: MentraBluetoothSDK, didUpdateGlasses _: GlassesRuntimeState) {}
139
+ func mentraBluetoothSDK(_: MentraBluetoothSDK, didUpdateSdkState _: PhoneSdkRuntimeState) {}
140
+ func mentraBluetoothSDK(_: MentraBluetoothSDK, didUpdateScan _: BluetoothScanState) {}
141
+ func mentraBluetoothSDK(_: MentraBluetoothSDK, didDiscover _: Device) {}
142
+ func mentraBluetoothSDK(_: MentraBluetoothSDK, didStopScan _: ScanStopReason) {}
143
+ func mentraBluetoothSDK(_: MentraBluetoothSDK, didReceive _: BluetoothEvent) {}
144
+ func mentraBluetoothSDK(_: MentraBluetoothSDK, didReceiveMicPcm _: MicPcmEvent) {}
145
+ func mentraBluetoothSDK(_: MentraBluetoothSDK, didReceiveMicLc3 _: MicLc3Event) {}
146
+ func mentraBluetoothSDK(_: MentraBluetoothSDK, didChangeDefaultDevice _: Device?) {}
147
+ func mentraBluetoothSDK(_: MentraBluetoothSDK, didLog _: String) {}
148
+ func mentraBluetoothSDK(_: MentraBluetoothSDK, didFail _: BluetoothError) {}
149
+ }