@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.
- package/README.md +45 -27
- package/android/build.gradle +2 -9
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +53 -36
- package/android/src/main/java/com/mentra/bluetoothsdk/Bridge.kt +22 -9
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +25 -36
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +9 -4
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +68 -56
- package/android/src/main/java/com/mentra/bluetoothsdk/audio/AudioModels.kt +125 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/camera/CameraModels.kt +209 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/connection/ConnectionModels.kt +78 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/controllers/ControllerManager.kt +4 -3
- package/android/src/main/java/com/mentra/bluetoothsdk/controllers/R1.kt +2 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/events/BluetoothEvents.kt +72 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/internal/MapParsing.kt +132 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/requests/DisplayRequests.kt +39 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G1.java +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G2.kt +2 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Mach1.java +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.java +168 -10
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraNex.kt +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/SGCManager.kt +7 -3
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Simulated.kt +3 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/status/DeviceStatus.kt +607 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/status/RuntimeState.kt +201 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/status/WifiHotspotStatus.kt +173 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/streaming/StreamModels.kt +348 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/stt/SherpaOnnxTranscriber.kt +1 -3
- package/android/src/main/java/com/mentra/bluetoothsdk/types/DeviceModels.kt +75 -0
- package/build/BluetoothSdk.types.d.ts +46 -20
- package/build/BluetoothSdk.types.d.ts.map +1 -1
- package/build/BluetoothSdk.types.js +1 -0
- package/build/BluetoothSdk.types.js.map +1 -1
- package/build/_private/BluetoothSdkModule.d.ts +6 -5
- package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
- package/build/_private/BluetoothSdkModule.js +40 -5
- package/build/_private/BluetoothSdkModule.js.map +1 -1
- package/build/index.d.ts +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +4 -2
- package/build/index.js.map +1 -1
- package/build/react/useMentraBluetooth.d.ts +5 -3
- package/build/react/useMentraBluetooth.d.ts.map +1 -1
- package/build/react/useMentraBluetooth.js +15 -4
- package/build/react/useMentraBluetooth.js.map +1 -1
- package/ios/BluetoothSdkModule.swift +45 -28
- package/ios/MentraBluetoothSDK.podspec +0 -3
- package/ios/Source/Audio/AudioModels.swift +159 -0
- package/ios/Source/Bridge.swift +22 -11
- package/ios/Source/Camera/CameraModels.swift +248 -0
- package/ios/Source/Connection/ScanSession.swift +27 -0
- package/ios/Source/DeviceManager.swift +20 -97
- package/ios/Source/DeviceStore.swift +13 -4
- package/ios/Source/Errors/BluetoothError.swift +19 -0
- package/ios/Source/Events/BluetoothEvents.swift +149 -0
- package/ios/Source/Internal/BluetoothAvailability.swift +58 -0
- package/ios/Source/Internal/ValueParsing.swift +90 -0
- package/ios/Source/MentraBluetoothSDK.swift +50 -2150
- package/ios/Source/Requests/DisplayRequests.swift +58 -0
- package/ios/Source/Status/DeviceStatus.swift +471 -0
- package/ios/Source/Status/RuntimeState.swift +359 -0
- package/ios/Source/Status/WifiHotspotStatus.swift +326 -0
- package/ios/Source/Streaming/StreamModels.swift +436 -0
- package/ios/Source/Types/DeviceModels.swift +134 -0
- package/ios/Source/controllers/ControllerManager.swift +3 -3
- package/ios/Source/controllers/R1.swift +1 -1
- package/ios/Source/sgcs/G1.swift +1 -1
- package/ios/Source/sgcs/G2.swift +99 -39
- package/ios/Source/sgcs/Mach1.swift +1 -1
- package/ios/Source/sgcs/MentraLive.swift +109 -12
- package/ios/Source/sgcs/MentraNex.swift +1 -1
- package/ios/Source/sgcs/SGCManager.swift +11 -3
- package/ios/Source/sgcs/Simulated.swift +2 -2
- package/ios/Source/stt/SherpaOnnxTranscriber.swift +1 -4
- package/package.json +1 -1
- package/src/BluetoothSdk.types.ts +48 -28
- package/src/_private/BluetoothSdkModule.ts +47 -21
- package/src/index.ts +6 -4
- package/src/react/useMentraBluetooth.ts +19 -7
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothModels.kt +0 -1810
- package/android/src/main/java/com/mentra/bluetoothsdk/stt/VadGateSpeechPolicy.kt +0 -221
- package/ios/Packages/VAD/Common/VAD/VADQuality.swift +0 -41
- package/ios/Packages/VAD/Common/VAD/VADState.swift +0 -26
- package/ios/Packages/VAD/Common/VAD/VADStrategy.swift +0 -29
- package/ios/Packages/VAD/Common/VAD/VADType.swift +0 -14
- package/ios/Packages/VAD/Data/Configuration.swift +0 -68
- package/ios/Packages/VAD/Data/FrameSize.swift +0 -39
- package/ios/Packages/VAD/Data/Record.swift +0 -13
- package/ios/Packages/VAD/Data/Result.swift +0 -22
- package/ios/Packages/VAD/Data/SampleRate.swift +0 -48
- package/ios/Packages/VAD/Silero/Model/silero_vad.onnx +0 -0
- package/ios/Packages/VAD/Silero/SileroVAD.swift +0 -284
- 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
|
|
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
|
-
|
|
441
|
-
if bypassVad {
|
|
442
|
-
handleSendingPcm(pcmData)
|
|
395
|
+
handleSendingPcm(pcmData)
|
|
443
396
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
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
|
-
_
|
|
1197
|
+
_ flash: Bool,
|
|
1198
|
+
_ sound: Bool,
|
|
1199
|
+
exposureTimeNs: Double? = nil
|
|
1284
1200
|
) {
|
|
1285
1201
|
Bridge.log(
|
|
1286
|
-
"MAN: requestPhoto
|
|
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
|
|
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:
|
|
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", "
|
|
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, "
|
|
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", "
|
|
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", "
|
|
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
|
+
}
|