@mentra/bluetooth-sdk 0.1.6 → 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 +16 -14
- package/android/build.gradle +2 -9
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +40 -31
- 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 +8 -4
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +34 -10
- package/android/src/main/java/com/mentra/bluetoothsdk/audio/AudioModels.kt +6 -6
- package/android/src/main/java/com/mentra/bluetoothsdk/camera/CameraModels.kt +29 -7
- 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 +12 -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 +14 -12
- package/android/src/main/java/com/mentra/bluetoothsdk/status/RuntimeState.kt +4 -2
- package/android/src/main/java/com/mentra/bluetoothsdk/streaming/StreamModels.kt +3 -3
- package/android/src/main/java/com/mentra/bluetoothsdk/stt/SherpaOnnxTranscriber.kt +1 -3
- package/build/BluetoothSdk.types.d.ts +42 -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 +37 -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 +41 -24
- package/ios/MentraBluetoothSDK.podspec +0 -3
- package/ios/Source/Audio/AudioModels.swift +10 -10
- package/ios/Source/Bridge.swift +22 -11
- package/ios/Source/Camera/CameraModels.swift +13 -11
- package/ios/Source/DeviceManager.swift +20 -97
- package/ios/Source/DeviceStore.swift +12 -4
- package/ios/Source/Events/BluetoothEvents.swift +34 -0
- package/ios/Source/MentraBluetoothSDK.swift +25 -10
- package/ios/Source/Status/DeviceStatus.swift +12 -4
- package/ios/Source/Status/RuntimeState.swift +22 -13
- package/ios/Source/Streaming/StreamModels.swift +5 -5
- 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 +2 -3
- 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 +44 -28
- package/src/_private/BluetoothSdkModule.ts +44 -21
- package/src/index.ts +6 -4
- package/src/react/useMentraBluetooth.ts +19 -7
- 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
|
@@ -903,6 +903,7 @@ class MentraLive: NSObject, SGCManager {
|
|
|
903
903
|
// BLOCK_AUDIO_DUPLEX: When true, suspends LC3 mic while phone is playing audio via A2DP
|
|
904
904
|
// to avoid overloading the MCU. Set to false to allow simultaneous A2DP + LC3 mic.
|
|
905
905
|
private let BLOCK_AUDIO_DUPLEX = false
|
|
906
|
+
private static let voiceActivityDetectionSwitchType = 8
|
|
906
907
|
|
|
907
908
|
var connectionState: String = ConnTypes.DISCONNECTED
|
|
908
909
|
|
|
@@ -1311,9 +1312,11 @@ class MentraLive: NSObject, SGCManager {
|
|
|
1311
1312
|
|
|
1312
1313
|
func requestPhoto(
|
|
1313
1314
|
_ requestId: String, appId: String, size: String?, webhookUrl: String?, authToken: String?,
|
|
1314
|
-
compress: String?, flash: Bool, sound: Bool
|
|
1315
|
+
compress: String?, flash: Bool, sound: Bool, exposureTimeNs: Double?
|
|
1315
1316
|
) {
|
|
1316
|
-
Bridge.log(
|
|
1317
|
+
Bridge.log(
|
|
1318
|
+
"LIVE: PHOTO PIPELINE [5/6] requestPhoto() entry requestId=\(requestId) appId=\(appId) flash=\(flash) sound=\(sound)"
|
|
1319
|
+
)
|
|
1317
1320
|
|
|
1318
1321
|
var json: [String: Any] = [
|
|
1319
1322
|
"type": "take_photo",
|
|
@@ -1360,7 +1363,13 @@ class MentraLive: NSObject, SGCManager {
|
|
|
1360
1363
|
json["flash"] = flash
|
|
1361
1364
|
json["sound"] = sound
|
|
1362
1365
|
|
|
1363
|
-
|
|
1366
|
+
if let e = exposureTimeNs, e.isFinite, e > 0, e <= Double(Int64.max) {
|
|
1367
|
+
Bridge.log("LIVE: Using manual exposure time for photo request \(requestId): \(Int64(e)) ns")
|
|
1368
|
+
json["exposureTimeNs"] = Int64(e)
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
Bridge.log("LIVE: PHOTO PIPELINE [5b/6] take_photo JSON ready bleImgId=\(bleImgId) transferMethod=auto")
|
|
1372
|
+
Bridge.log("LIVE: PHOTO PIPELINE [6/6] Dispatching take_photo to sendJson()")
|
|
1364
1373
|
|
|
1365
1374
|
sendJson(json, wakeUp: true)
|
|
1366
1375
|
}
|
|
@@ -1814,6 +1823,14 @@ class MentraLive: NSObject, SGCManager {
|
|
|
1814
1823
|
let isCharging = json["charging"] as? Bool ?? charging
|
|
1815
1824
|
updateBatteryStatus(level: level, isCharging: isCharging)
|
|
1816
1825
|
|
|
1826
|
+
case "voice_activity_detection_status":
|
|
1827
|
+
let enabled = json["voiceActivityDetectionEnabled"] as? Bool ?? true
|
|
1828
|
+
handleVoiceActivityDetectionStatus(enabled: enabled)
|
|
1829
|
+
|
|
1830
|
+
case "speaking_status":
|
|
1831
|
+
let speaking = json["speaking"] as? Bool ?? false
|
|
1832
|
+
handleSpeakingStatus(speaking: speaking)
|
|
1833
|
+
|
|
1817
1834
|
case "wifi_status":
|
|
1818
1835
|
let connected = json["connected"] as? Bool ?? false
|
|
1819
1836
|
let ssid = json["ssid"] as? String ?? ""
|
|
@@ -1893,9 +1910,7 @@ class MentraLive: NSObject, SGCManager {
|
|
|
1893
1910
|
let switchType = (json["switch_type"] as? Int) ?? (json["switchType"] as? Int) ?? -1
|
|
1894
1911
|
let switchValue = (json["switch_value"] as? Int) ?? (json["switchValue"] as? Int) ?? -1
|
|
1895
1912
|
let timestamp = parseTimestamp(json["timestamp"])
|
|
1896
|
-
|
|
1897
|
-
switchType: switchType, value: switchValue, timestamp: timestamp
|
|
1898
|
-
)
|
|
1913
|
+
handleSwitchStatus(switchType: switchType, value: switchValue, timestamp: timestamp)
|
|
1899
1914
|
|
|
1900
1915
|
case "rgb_led_control_response":
|
|
1901
1916
|
let requestId = json["requestId"] as? String ?? ""
|
|
@@ -2087,6 +2102,9 @@ class MentraLive: NSObject, SGCManager {
|
|
|
2087
2102
|
// Note: Stored separately from BES version for OTA patch matching
|
|
2088
2103
|
DeviceStore.shared.apply("glasses", "mtkFirmwareVersion", mtkFirmwareVersion)
|
|
2089
2104
|
}
|
|
2105
|
+
if let systemTimeMs = fields["system_time_ms"] as? NSNumber {
|
|
2106
|
+
DeviceStore.shared.apply("glasses", "systemTimeMs", systemTimeMs.int64Value)
|
|
2107
|
+
}
|
|
2090
2108
|
if let bluetoothMacAddress = fields["bt_mac_address"] as? String {
|
|
2091
2109
|
DeviceStore.shared.apply("glasses", "bluetoothMacAddress", bluetoothMacAddress)
|
|
2092
2110
|
}
|
|
@@ -2241,6 +2259,25 @@ class MentraLive: NSObject, SGCManager {
|
|
|
2241
2259
|
case "sr_vol":
|
|
2242
2260
|
handleSrVol(json)
|
|
2243
2261
|
|
|
2262
|
+
case "sr_vad":
|
|
2263
|
+
if let bodyObj = k900ParseBody(json["B"]),
|
|
2264
|
+
let on = k900JsonInt(bodyObj, "on"),
|
|
2265
|
+
on == 0 || on == 1
|
|
2266
|
+
{
|
|
2267
|
+
handleSpeakingStatus(speaking: on == 1)
|
|
2268
|
+
}
|
|
2269
|
+
|
|
2270
|
+
case "sr_swit":
|
|
2271
|
+
if let body = k900ParseBody(json["B"]) {
|
|
2272
|
+
let switchType = k900JsonInt(body, "type") ?? -1
|
|
2273
|
+
let switchValue = k900JsonInt(body, "switch") ?? -1
|
|
2274
|
+
handleSwitchStatus(
|
|
2275
|
+
switchType: switchType,
|
|
2276
|
+
value: switchValue,
|
|
2277
|
+
timestamp: Int64(Date().timeIntervalSince1970 * 1000)
|
|
2278
|
+
)
|
|
2279
|
+
}
|
|
2280
|
+
|
|
2244
2281
|
case "sr_shut":
|
|
2245
2282
|
Bridge.log("K900 shutdown command received - glasses shutting down")
|
|
2246
2283
|
// Mark as killed to prevent reconnection attempts
|
|
@@ -2461,7 +2498,7 @@ class MentraLive: NSObject, SGCManager {
|
|
|
2461
2498
|
}
|
|
2462
2499
|
|
|
2463
2500
|
func sendGalleryMode() {
|
|
2464
|
-
let active = DeviceStore.shared.get("bluetooth", "
|
|
2501
|
+
let active = DeviceStore.shared.get("bluetooth", "gallery_mode") as! Bool
|
|
2465
2502
|
Bridge.log("LIVE: 📸 Sending gallery mode active to glasses: \(active)")
|
|
2466
2503
|
|
|
2467
2504
|
let json: [String: Any] = [
|
|
@@ -2656,7 +2693,7 @@ class MentraLive: NSObject, SGCManager {
|
|
|
2656
2693
|
// return
|
|
2657
2694
|
// }
|
|
2658
2695
|
|
|
2659
|
-
// // Forward PCM data to DeviceManager for
|
|
2696
|
+
// // Forward PCM data to DeviceManager for audio events and server transmission (same as Android)
|
|
2660
2697
|
// DeviceManager.shared.handlePcm(pcmData)
|
|
2661
2698
|
|
|
2662
2699
|
// Bridge.log(
|
|
@@ -3237,6 +3274,9 @@ class MentraLive: NSObject, SGCManager {
|
|
|
3237
3274
|
Bridge.log("LIVE: All chunks queued for transmission")
|
|
3238
3275
|
} else {
|
|
3239
3276
|
// Normal single message transmission
|
|
3277
|
+
if (json["type"] as? String) == "take_photo" {
|
|
3278
|
+
Bridge.log("LIVE: PHOTO PIPELINE BLE handoff — sendJson -> queueSend take_photo")
|
|
3279
|
+
}
|
|
3240
3280
|
Bridge.log("LIVE: Sending data to glasses: \(jsonString)")
|
|
3241
3281
|
let packedData = packJson(jsonString, wakeUp: wakeUp) ?? Data()
|
|
3242
3282
|
queueSend(packedData, id: trackingId)
|
|
@@ -3508,6 +3548,27 @@ class MentraLive: NSObject, SGCManager {
|
|
|
3508
3548
|
}
|
|
3509
3549
|
}
|
|
3510
3550
|
|
|
3551
|
+
private func handleVoiceActivityDetectionStatus(enabled: Bool) {
|
|
3552
|
+
Bridge.log("LIVE: Voice Activity Detection \(enabled ? "enabled" : "disabled")")
|
|
3553
|
+
Bridge.sendVoiceActivityDetectionStatus(enabled)
|
|
3554
|
+
}
|
|
3555
|
+
|
|
3556
|
+
private func handleSpeakingStatus(speaking: Bool) {
|
|
3557
|
+
guard voiceActivityDetectionEnabled else {
|
|
3558
|
+
Bridge.log("LIVE: Ignoring speaking status because Voice Activity Detection is disabled")
|
|
3559
|
+
return
|
|
3560
|
+
}
|
|
3561
|
+
Bridge.log("LIVE: Speaking status \(speaking ? "speaking" : "not speaking")")
|
|
3562
|
+
Bridge.sendSpeakingStatus(speaking)
|
|
3563
|
+
}
|
|
3564
|
+
|
|
3565
|
+
private func handleSwitchStatus(switchType: Int, value: Int, timestamp: Int64) {
|
|
3566
|
+
Bridge.sendSwitchStatus(switchType: switchType, value: value, timestamp: timestamp)
|
|
3567
|
+
if switchType == Self.voiceActivityDetectionSwitchType, value == 0 || value == 1 {
|
|
3568
|
+
handleVoiceActivityDetectionStatus(enabled: value == 1)
|
|
3569
|
+
}
|
|
3570
|
+
}
|
|
3571
|
+
|
|
3511
3572
|
private func updateWifiStatus(connected: Bool, ssid: String, ip: String) {
|
|
3512
3573
|
Bridge.log("LIVE: 🌐 Updating WiFi status - connected: \(connected), ssid: \(ssid)")
|
|
3513
3574
|
DeviceStore.shared.apply("glasses", "wifiConnected", connected)
|
|
@@ -4493,6 +4554,42 @@ extension MentraLive {
|
|
|
4493
4554
|
|
|
4494
4555
|
// Send gallery mode state (camera app running status)
|
|
4495
4556
|
sendGalleryMode()
|
|
4557
|
+
|
|
4558
|
+
// Send glasses-side Voice Activity Detection setting.
|
|
4559
|
+
sendVoiceActivityDetectionSetting()
|
|
4560
|
+
}
|
|
4561
|
+
|
|
4562
|
+
func sendVoiceActivityDetectionSetting() {
|
|
4563
|
+
let enabled = DeviceStore.shared.get("bluetooth", "voice_activity_detection_enabled") as? Bool ?? true
|
|
4564
|
+
Bridge.log("LIVE: 🎤 Sending Voice Activity Detection setting to glasses: \(enabled)")
|
|
4565
|
+
|
|
4566
|
+
guard connectedPeripheral != nil, txCharacteristic != nil else {
|
|
4567
|
+
Bridge.log("Cannot send Voice Activity Detection setting - BLE write path not ready")
|
|
4568
|
+
return
|
|
4569
|
+
}
|
|
4570
|
+
|
|
4571
|
+
do {
|
|
4572
|
+
let bodyData = try JSONSerialization.data(withJSONObject: [
|
|
4573
|
+
"type": Self.voiceActivityDetectionSwitchType,
|
|
4574
|
+
"switch": enabled ? 1 : 0,
|
|
4575
|
+
])
|
|
4576
|
+
guard let bodyString = String(data: bodyData, encoding: .utf8) else {
|
|
4577
|
+
Bridge.log("LIVE: Failed to encode Voice Activity Detection payload")
|
|
4578
|
+
return
|
|
4579
|
+
}
|
|
4580
|
+
let command: [String: Any] = [
|
|
4581
|
+
"C": "cs_swit",
|
|
4582
|
+
"V": 1,
|
|
4583
|
+
"B": bodyString,
|
|
4584
|
+
]
|
|
4585
|
+
if sendRawK900Command(command, wakeUp: true) {
|
|
4586
|
+
Bridge.sendVoiceActivityDetectionStatus(enabled)
|
|
4587
|
+
} else {
|
|
4588
|
+
Bridge.log("LIVE: Failed to send Voice Activity Detection setting command")
|
|
4589
|
+
}
|
|
4590
|
+
} catch {
|
|
4591
|
+
Bridge.log("LIVE: Error encoding Voice Activity Detection payload: \(error)")
|
|
4592
|
+
}
|
|
4496
4593
|
}
|
|
4497
4594
|
|
|
4498
4595
|
func sendButtonVideoRecordingSettings() {
|
|
@@ -4500,11 +4597,11 @@ extension MentraLive {
|
|
|
4500
4597
|
DeviceStore.shared.get("bluetooth", "button_video_settings") as? [String: Any] ?? [
|
|
4501
4598
|
"width": 1280,
|
|
4502
4599
|
"height": 720,
|
|
4503
|
-
"
|
|
4600
|
+
"fps": 30,
|
|
4504
4601
|
]
|
|
4505
4602
|
let width = settings["width"] as? Int ?? 1280
|
|
4506
4603
|
let height = settings["height"] as? Int ?? 720
|
|
4507
|
-
let fps = settings["
|
|
4604
|
+
let fps = settings["fps"] as? Int ?? 30
|
|
4508
4605
|
|
|
4509
4606
|
// Use defaults if not set
|
|
4510
4607
|
let finalWidth = width > 0 ? width : 1280
|
|
@@ -4582,9 +4679,9 @@ extension MentraLive {
|
|
|
4582
4679
|
}
|
|
4583
4680
|
|
|
4584
4681
|
func sendCameraFovSetting() {
|
|
4585
|
-
let settings = DeviceStore.shared.get("bluetooth", "camera_fov") as? [String: Any] ?? ["fov": 118, "
|
|
4682
|
+
let settings = DeviceStore.shared.get("bluetooth", "camera_fov") as? [String: Any] ?? ["fov": 118, "roi_position": 0]
|
|
4586
4683
|
let fov = settings["fov"] as? Int ?? 118
|
|
4587
|
-
let roiPosition = settings["
|
|
4684
|
+
let roiPosition = settings["roi_position"] as? Int ?? 0
|
|
4588
4685
|
|
|
4589
4686
|
Bridge.log("Sending camera FOV setting: fov=\(fov), roiPosition=\(roiPosition)")
|
|
4590
4687
|
|
|
@@ -46,7 +46,7 @@ class MentraNexSGC: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate, SG
|
|
|
46
46
|
|
|
47
47
|
func requestPhoto(
|
|
48
48
|
_: String, appId _: String, size _: String?, webhookUrl _: String?, authToken _: String?,
|
|
49
|
-
compress _: String?, flash _: Bool, sound _: Bool
|
|
49
|
+
compress _: String?, flash _: Bool, sound _: Bool, exposureTimeNs _: Double?
|
|
50
50
|
) {}
|
|
51
51
|
|
|
52
52
|
func startStream(_: [String: Any]) {}
|
|
@@ -20,7 +20,7 @@ protocol SGCManager {
|
|
|
20
20
|
|
|
21
21
|
func requestPhoto(
|
|
22
22
|
_ requestId: String, appId: String, size: String?, webhookUrl: String?, authToken: String?,
|
|
23
|
-
compress: String?, flash: Bool, sound: Bool
|
|
23
|
+
compress: String?, flash: Bool, sound: Bool, exposureTimeNs: Double?
|
|
24
24
|
)
|
|
25
25
|
func startStream(_ message: [String: Any])
|
|
26
26
|
func stopStream()
|
|
@@ -103,6 +103,10 @@ protocol SGCManager {
|
|
|
103
103
|
func queryGalleryStatus()
|
|
104
104
|
func sendGalleryMode()
|
|
105
105
|
|
|
106
|
+
// MARK: - Voice Activity Detection
|
|
107
|
+
|
|
108
|
+
func sendVoiceActivityDetectionSetting()
|
|
109
|
+
|
|
106
110
|
// MARK: - Version Info
|
|
107
111
|
|
|
108
112
|
func requestVersionInfo()
|
|
@@ -127,6 +131,10 @@ extension SGCManager {
|
|
|
127
131
|
|
|
128
132
|
func setDashboardMenu(_: [[String: Any]]) {}
|
|
129
133
|
|
|
134
|
+
// MARK: - Voice Activity Detection (default no-op — Mentra Live supports this)
|
|
135
|
+
|
|
136
|
+
func sendVoiceActivityDetectionSetting() {}
|
|
137
|
+
|
|
130
138
|
// MARK: - Default DeviceStore-backed property implementations
|
|
131
139
|
|
|
132
140
|
var fullyBooted: Bool {
|
|
@@ -181,8 +189,8 @@ extension SGCManager {
|
|
|
181
189
|
DeviceStore.shared.get("glasses", "micEnabled") as? Bool ?? false
|
|
182
190
|
}
|
|
183
191
|
|
|
184
|
-
var
|
|
185
|
-
DeviceStore.shared.get("glasses", "
|
|
192
|
+
var voiceActivityDetectionEnabled: Bool {
|
|
193
|
+
DeviceStore.shared.get("glasses", "voiceActivityDetectionEnabled") as? Bool ?? true
|
|
186
194
|
}
|
|
187
195
|
|
|
188
196
|
var batteryLevel: Int {
|
|
@@ -12,7 +12,7 @@ class Simulated: SGCManager {
|
|
|
12
12
|
DeviceStore.shared.apply("glasses", "connected", true)
|
|
13
13
|
DeviceStore.shared.apply("glasses", "connectionState", ConnTypes.CONNECTED)
|
|
14
14
|
DeviceStore.shared.apply("glasses", "micEnabled", false)
|
|
15
|
-
DeviceStore.shared.apply("glasses", "
|
|
15
|
+
DeviceStore.shared.apply("glasses", "voiceActivityDetectionEnabled", true)
|
|
16
16
|
DeviceStore.shared.apply("glasses", "bluetoothClassicConnected", false)
|
|
17
17
|
}
|
|
18
18
|
|
|
@@ -75,7 +75,7 @@ class Simulated: SGCManager {
|
|
|
75
75
|
|
|
76
76
|
// MARK: - Camera & Media
|
|
77
77
|
|
|
78
|
-
func requestPhoto(_: String, appId _: String, size _: String?, webhookUrl _: String?, authToken _: String?, compress _: String?, flash _: Bool, sound _: Bool) {
|
|
78
|
+
func requestPhoto(_: String, appId _: String, size _: String?, webhookUrl _: String?, authToken _: String?, compress _: String?, flash _: Bool, sound _: Bool, exposureTimeNs _: Double?) {
|
|
79
79
|
Bridge.log("requestPhoto")
|
|
80
80
|
}
|
|
81
81
|
|
|
@@ -214,16 +214,13 @@ class SherpaOnnxTranscriber {
|
|
|
214
214
|
* Feed PCM audio data (16-bit little endian) into the transcriber.
|
|
215
215
|
* This method should be called continuously with short chunks (e.g., 100-300ms).
|
|
216
216
|
*
|
|
217
|
-
*
|
|
218
|
-
* so it's directly queued for processing without additional VAD checks.
|
|
217
|
+
* Audio is queued directly; microphone VAD gating is not applied in the SDK.
|
|
219
218
|
*/
|
|
220
219
|
func acceptAudio(pcm16le: Data) {
|
|
221
220
|
guard isRunning else {
|
|
222
221
|
return
|
|
223
222
|
}
|
|
224
223
|
|
|
225
|
-
// Directly queue the audio data for processing
|
|
226
|
-
// No VAD check here as it's assumed to be done upstream
|
|
227
224
|
queueAudioData(pcm16le)
|
|
228
225
|
}
|
|
229
226
|
|
package/package.json
CHANGED
|
@@ -22,9 +22,15 @@ export type HeadUpEvent = {
|
|
|
22
22
|
up: boolean
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
export type
|
|
26
|
-
type: "
|
|
27
|
-
|
|
25
|
+
export type VoiceActivityDetectionStatusEvent = {
|
|
26
|
+
type: "voice_activity_detection_status"
|
|
27
|
+
voiceActivityDetectionEnabled: boolean
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type SpeakingStatusEvent = {
|
|
31
|
+
type: "speaking_status"
|
|
32
|
+
speaking: boolean
|
|
33
|
+
timestamp: number
|
|
28
34
|
}
|
|
29
35
|
|
|
30
36
|
export type BatteryStatusEvent = {
|
|
@@ -61,6 +67,7 @@ export function createDisconnectedGlassesStatus(): Partial<GlassesStatus> {
|
|
|
61
67
|
return {
|
|
62
68
|
connection: {state: 'disconnected'},
|
|
63
69
|
hotspot: {state: 'disabled'},
|
|
70
|
+
voiceActivityDetectionEnabled: true,
|
|
64
71
|
wifi: {state: 'disconnected'},
|
|
65
72
|
}
|
|
66
73
|
}
|
|
@@ -190,8 +197,6 @@ export type RgbLedControlResponseEvent =
|
|
|
190
197
|
|
|
191
198
|
export type RgbLedAction = "on" | "off"
|
|
192
199
|
export type RgbLedColor = "red" | "green" | "blue" | "orange" | "white"
|
|
193
|
-
/** `"auto"` enables local button photo/video capture; `"manual"` reports button events without local gallery capture. */
|
|
194
|
-
export type GalleryMode = "auto" | "manual"
|
|
195
200
|
export type PhotoSize = "small" | "medium" | "large" | "full"
|
|
196
201
|
export type ButtonPhotoSize = "small" | "medium" | "large"
|
|
197
202
|
export type PhotoCompression = "none" | "medium" | "heavy"
|
|
@@ -223,14 +228,30 @@ export type CameraFovSetting = {
|
|
|
223
228
|
roiPosition: number
|
|
224
229
|
}
|
|
225
230
|
|
|
231
|
+
type NativeCameraFovSetting = {
|
|
232
|
+
fov: number
|
|
233
|
+
roi_position: number
|
|
234
|
+
}
|
|
235
|
+
|
|
226
236
|
export type MicPreference = "auto" | "phone" | "glasses" | "bluetooth"
|
|
227
237
|
export type MicMode = "phone" | "glasses" | "bluetoothClassic" | "bluetooth"
|
|
228
238
|
|
|
239
|
+
export type PhotoRequestParams = {
|
|
240
|
+
requestId: string
|
|
241
|
+
appId: string
|
|
242
|
+
size: PhotoSize
|
|
243
|
+
webhookUrl: string | null
|
|
244
|
+
authToken: string | null
|
|
245
|
+
compress: PhotoCompression
|
|
246
|
+
sound: boolean
|
|
247
|
+
exposureTimeNs?: number | null
|
|
248
|
+
}
|
|
249
|
+
|
|
229
250
|
export type StreamVideoConfig = {
|
|
230
251
|
width?: number
|
|
231
252
|
height?: number
|
|
232
253
|
bitrate?: number
|
|
233
|
-
|
|
254
|
+
fps?: number
|
|
234
255
|
}
|
|
235
256
|
|
|
236
257
|
export type StreamAudioConfig = {
|
|
@@ -299,7 +320,7 @@ export type MicPcmEvent = {
|
|
|
299
320
|
bitsPerSample: 16
|
|
300
321
|
channels: 1
|
|
301
322
|
encoding: "pcm_s16le"
|
|
302
|
-
|
|
323
|
+
voiceActivityDetectionEnabled: boolean
|
|
303
324
|
}
|
|
304
325
|
|
|
305
326
|
export type MicLc3Event = {
|
|
@@ -312,7 +333,7 @@ export type MicLc3Event = {
|
|
|
312
333
|
frameSizeBytes: number
|
|
313
334
|
bitrate: number
|
|
314
335
|
packetizedFromGlasses: boolean
|
|
315
|
-
|
|
336
|
+
voiceActivityDetectionEnabled: boolean
|
|
316
337
|
}
|
|
317
338
|
|
|
318
339
|
export type StreamStatusLifecycleState = "initializing" | "streaming" | "stopping" | "stopped"
|
|
@@ -450,7 +471,8 @@ export type BluetoothSdkModuleEvents = {
|
|
|
450
471
|
button_press: (event: ButtonPressEvent) => void
|
|
451
472
|
touch_event: (event: TouchEvent) => void
|
|
452
473
|
head_up: (event: HeadUpEvent) => void
|
|
453
|
-
|
|
474
|
+
voice_activity_detection_status: (event: VoiceActivityDetectionStatusEvent) => void
|
|
475
|
+
speaking_status: (event: SpeakingStatusEvent) => void
|
|
454
476
|
battery_status: (event: BatteryStatusEvent) => void
|
|
455
477
|
local_transcription: (event: LocalTranscriptionEvent) => void
|
|
456
478
|
wifi_status_change: (event: WifiStatusChangeEvent) => void
|
|
@@ -500,7 +522,7 @@ export type PublicBluetoothStatus = Pick<
|
|
|
500
522
|
| "wifiScanResults"
|
|
501
523
|
| "lastLog"
|
|
502
524
|
| "otherBtConnected"
|
|
503
|
-
| "
|
|
525
|
+
| "galleryModeEnabled"
|
|
504
526
|
>
|
|
505
527
|
|
|
506
528
|
export type BluetoothSdkEventMap = {
|
|
@@ -511,7 +533,8 @@ export type BluetoothSdkEventMap = {
|
|
|
511
533
|
button_press: ButtonPressEvent
|
|
512
534
|
touch_event: TouchEvent
|
|
513
535
|
head_up: HeadUpEvent
|
|
514
|
-
|
|
536
|
+
voice_activity_detection_status: VoiceActivityDetectionStatusEvent
|
|
537
|
+
speaking_status: SpeakingStatusEvent
|
|
515
538
|
battery_status: BatteryStatusEvent
|
|
516
539
|
local_transcription: LocalTranscriptionEvent
|
|
517
540
|
wifi_status_change: WifiStatusChangeEvent
|
|
@@ -577,22 +600,15 @@ export interface BluetoothSdkPublicModule {
|
|
|
577
600
|
forgetWifiNetwork(ssid: string): Promise<void>
|
|
578
601
|
setHotspotState(enabled: boolean): Promise<void>
|
|
579
602
|
|
|
580
|
-
|
|
603
|
+
setGalleryModeEnabled(enabled: boolean): Promise<void>
|
|
604
|
+
setVoiceActivityDetectionEnabled(enabled: boolean): Promise<void>
|
|
581
605
|
setButtonPhotoSettings(size: ButtonPhotoSize): Promise<void>
|
|
582
|
-
setButtonVideoRecordingSettings(width: number, height: number,
|
|
606
|
+
setButtonVideoRecordingSettings(width: number, height: number, fps: number): Promise<void>
|
|
583
607
|
setButtonCameraLed(enabled: boolean): Promise<void>
|
|
584
608
|
setButtonMaxRecordingTime(minutes: number): Promise<void>
|
|
585
609
|
setCameraFov(fov: CameraFov): Promise<void>
|
|
586
610
|
queryGalleryStatus(): Promise<void>
|
|
587
|
-
requestPhoto(
|
|
588
|
-
requestId: string,
|
|
589
|
-
appId: string,
|
|
590
|
-
size: PhotoSize,
|
|
591
|
-
webhookUrl: string | null,
|
|
592
|
-
authToken: string | null,
|
|
593
|
-
compress: PhotoCompression,
|
|
594
|
-
sound: boolean,
|
|
595
|
-
): Promise<void>
|
|
611
|
+
requestPhoto(params: PhotoRequestParams): Promise<void>
|
|
596
612
|
startVideoRecording(requestId: string, save: boolean, sound: boolean): Promise<void>
|
|
597
613
|
stopVideoRecording(requestId: string): Promise<void>
|
|
598
614
|
|
|
@@ -603,7 +619,6 @@ export interface BluetoothSdkPublicModule {
|
|
|
603
619
|
setMicState(
|
|
604
620
|
enabled: boolean,
|
|
605
621
|
useGlassesMic?: boolean,
|
|
606
|
-
bypassVad?: boolean,
|
|
607
622
|
sendTranscript?: boolean,
|
|
608
623
|
sendLc3Data?: boolean,
|
|
609
624
|
): Promise<void>
|
|
@@ -664,6 +679,7 @@ export interface GlassesStatus {
|
|
|
664
679
|
// state:
|
|
665
680
|
connection: GlassesConnectionStatus
|
|
666
681
|
micEnabled: boolean
|
|
682
|
+
voiceActivityDetectionEnabled: boolean
|
|
667
683
|
bluetoothClassicConnected: boolean
|
|
668
684
|
signalStrength: number
|
|
669
685
|
/** Milliseconds since epoch when signalStrength was last refreshed by the phone BLE stack. */
|
|
@@ -787,7 +803,7 @@ export interface BluetoothStatus {
|
|
|
787
803
|
lastLog: string[]
|
|
788
804
|
otherBtConnected: boolean
|
|
789
805
|
// desired settings the SDK sends to compatible connected glasses:
|
|
790
|
-
|
|
806
|
+
galleryModeEnabled: boolean
|
|
791
807
|
}
|
|
792
808
|
|
|
793
809
|
export type BluetoothSettingsUpdate = Partial<{
|
|
@@ -805,19 +821,19 @@ export type BluetoothSettingsUpdate = Partial<{
|
|
|
805
821
|
dashboard_height: number
|
|
806
822
|
dashboard_depth: number
|
|
807
823
|
menu_apps: DashboardMenuItem[] | CoreDashboardMenuItem[] | Array<Record<string, unknown>> | null
|
|
808
|
-
|
|
824
|
+
gallery_mode: boolean
|
|
825
|
+
voice_activity_detection_enabled: boolean
|
|
809
826
|
button_photo_size: ButtonPhotoSize
|
|
810
|
-
button_video_settings: {width: number; height: number;
|
|
827
|
+
button_video_settings: {width: number; height: number; fps: number}
|
|
811
828
|
button_video_width: number
|
|
812
829
|
button_video_height: number
|
|
813
830
|
button_video_fps: number
|
|
814
831
|
button_camera_led: boolean
|
|
815
832
|
button_max_recording_time: number
|
|
816
|
-
camera_fov:
|
|
833
|
+
camera_fov: NativeCameraFovSetting
|
|
817
834
|
should_send_pcm: boolean
|
|
818
835
|
should_send_lc3: boolean
|
|
819
836
|
should_send_transcript: boolean
|
|
820
|
-
bypass_vad: boolean
|
|
821
837
|
offline_mode: boolean
|
|
822
838
|
offline_captions_running: boolean
|
|
823
839
|
local_stt_fallback_active: boolean
|
|
@@ -12,14 +12,12 @@ import {
|
|
|
12
12
|
DashboardMenuItem,
|
|
13
13
|
Device,
|
|
14
14
|
DeviceModel,
|
|
15
|
-
GalleryMode,
|
|
16
15
|
GlassesMediaVolumeGetResult,
|
|
17
16
|
GlassesMediaVolumeSetResult,
|
|
18
17
|
GlassesStatus,
|
|
19
18
|
MicPreference,
|
|
20
19
|
ObservableStoreCategory,
|
|
21
|
-
|
|
22
|
-
PhotoSize,
|
|
20
|
+
PhotoRequestParams,
|
|
23
21
|
PublicBluetoothStatus,
|
|
24
22
|
RgbLedAction,
|
|
25
23
|
RgbLedColor,
|
|
@@ -96,22 +94,15 @@ declare class BluetoothSdkNativeModule extends NativeModule<BluetoothSdkModuleEv
|
|
|
96
94
|
logCurrentWifiFrequency(): Promise<void>
|
|
97
95
|
|
|
98
96
|
// Gallery Commands
|
|
99
|
-
|
|
97
|
+
setGalleryModeEnabled(enabled: boolean): Promise<void>
|
|
98
|
+
setVoiceActivityDetectionEnabled(enabled: boolean): Promise<void>
|
|
100
99
|
setButtonPhotoSettings(size: ButtonPhotoSize): Promise<void>
|
|
101
|
-
setButtonVideoRecordingSettings(width: number, height: number,
|
|
100
|
+
setButtonVideoRecordingSettings(width: number, height: number, fps: number): Promise<void>
|
|
102
101
|
setButtonCameraLed(enabled: boolean): Promise<void>
|
|
103
102
|
setButtonMaxRecordingTime(minutes: number): Promise<void>
|
|
104
103
|
setCameraFov(fov: CameraFov): Promise<void>
|
|
105
104
|
queryGalleryStatus(): Promise<void>
|
|
106
|
-
requestPhoto(
|
|
107
|
-
requestId: string,
|
|
108
|
-
appId: string,
|
|
109
|
-
size: PhotoSize,
|
|
110
|
-
webhookUrl: string | null,
|
|
111
|
-
authToken: string | null,
|
|
112
|
-
compress: PhotoCompression,
|
|
113
|
-
sound: boolean,
|
|
114
|
-
): Promise<void>
|
|
105
|
+
requestPhoto(params: PhotoRequestParams): Promise<void>
|
|
115
106
|
|
|
116
107
|
// OTA Commands
|
|
117
108
|
sendOtaStart(): Promise<void>
|
|
@@ -133,7 +124,6 @@ declare class BluetoothSdkNativeModule extends NativeModule<BluetoothSdkModuleEv
|
|
|
133
124
|
setMicState(
|
|
134
125
|
enabled: boolean,
|
|
135
126
|
useGlassesMic?: boolean,
|
|
136
|
-
bypassVad?: boolean,
|
|
137
127
|
sendTranscript?: boolean,
|
|
138
128
|
sendLc3Data?: boolean,
|
|
139
129
|
): Promise<void>
|
|
@@ -301,7 +291,6 @@ NativeBluetoothSdkModule.getDefaultDevice = function () {
|
|
|
301
291
|
const nativeSetMicState = NativeBluetoothSdkModule.setMicState.bind(NativeBluetoothSdkModule) as (
|
|
302
292
|
enabled: boolean,
|
|
303
293
|
useGlassesMic: boolean,
|
|
304
|
-
bypassVad: boolean,
|
|
305
294
|
sendTranscript: boolean,
|
|
306
295
|
sendLc3Data: boolean,
|
|
307
296
|
) => MaybePromise<void>
|
|
@@ -355,15 +344,23 @@ NativeBluetoothSdkModule.setScreenDisabled = function (disabled: boolean) {
|
|
|
355
344
|
return this.updateBluetoothSettings({screen_disabled: disabled})
|
|
356
345
|
}
|
|
357
346
|
|
|
347
|
+
NativeBluetoothSdkModule.setGalleryModeEnabled = function (enabled: boolean) {
|
|
348
|
+
return this.updateBluetoothSettings({gallery_mode: enabled})
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
NativeBluetoothSdkModule.setVoiceActivityDetectionEnabled = function (enabled: boolean) {
|
|
352
|
+
return this.updateBluetoothSettings({voice_activity_detection_enabled: enabled})
|
|
353
|
+
}
|
|
354
|
+
|
|
358
355
|
NativeBluetoothSdkModule.setButtonPhotoSettings = function (size: ButtonPhotoSize) {
|
|
359
356
|
return this.updateBluetoothSettings({button_photo_size: size})
|
|
360
357
|
}
|
|
361
358
|
|
|
362
|
-
NativeBluetoothSdkModule.setButtonVideoRecordingSettings = function (width: number, height: number,
|
|
359
|
+
NativeBluetoothSdkModule.setButtonVideoRecordingSettings = function (width: number, height: number, fps: number) {
|
|
363
360
|
return this.updateBluetoothSettings({
|
|
364
361
|
button_video_width: width,
|
|
365
362
|
button_video_height: height,
|
|
366
|
-
button_video_fps:
|
|
363
|
+
button_video_fps: fps,
|
|
367
364
|
})
|
|
368
365
|
}
|
|
369
366
|
|
|
@@ -376,13 +373,15 @@ NativeBluetoothSdkModule.setButtonMaxRecordingTime = function (minutes: number)
|
|
|
376
373
|
}
|
|
377
374
|
|
|
378
375
|
NativeBluetoothSdkModule.setCameraFov = function (fov: CameraFov) {
|
|
379
|
-
|
|
376
|
+
const setting = CAMERA_FOV_SETTINGS[fov]
|
|
377
|
+
return this.updateBluetoothSettings({
|
|
378
|
+
camera_fov: {fov: setting.fov, roi_position: setting.roiPosition},
|
|
379
|
+
})
|
|
380
380
|
}
|
|
381
381
|
|
|
382
382
|
NativeBluetoothSdkModule.setMicState = function (
|
|
383
383
|
enabled: boolean,
|
|
384
384
|
useGlassesMic?: boolean,
|
|
385
|
-
bypassVad?: boolean,
|
|
386
385
|
sendTranscript?: boolean,
|
|
387
386
|
sendLc3Data?: boolean,
|
|
388
387
|
) {
|
|
@@ -390,7 +389,6 @@ NativeBluetoothSdkModule.setMicState = function (
|
|
|
390
389
|
nativeSetMicState(
|
|
391
390
|
enabled,
|
|
392
391
|
useGlassesMic ?? true,
|
|
393
|
-
bypassVad ?? true,
|
|
394
392
|
sendTranscript ?? false,
|
|
395
393
|
sendLc3Data ?? false,
|
|
396
394
|
),
|
|
@@ -487,5 +485,30 @@ NativeBluetoothSdkModule.scan = async function (
|
|
|
487
485
|
})
|
|
488
486
|
}
|
|
489
487
|
|
|
488
|
+
/** Expo Android bridge rejects null values in Map<String, Any> — omit optional nullish fields. */
|
|
489
|
+
function photoRequestParamsForNative(params: PhotoRequestParams): Record<string, string | number | boolean> {
|
|
490
|
+
const payload: Record<string, string | number | boolean> = {
|
|
491
|
+
requestId: params.requestId,
|
|
492
|
+
appId: params.appId,
|
|
493
|
+
size: params.size,
|
|
494
|
+
webhookUrl: params.webhookUrl ?? "",
|
|
495
|
+
compress: params.compress,
|
|
496
|
+
flash: true,
|
|
497
|
+
sound: params.sound,
|
|
498
|
+
}
|
|
499
|
+
if (params.authToken != null && params.authToken.length > 0) {
|
|
500
|
+
payload.authToken = params.authToken
|
|
501
|
+
}
|
|
502
|
+
if (params.exposureTimeNs != null && Number.isFinite(params.exposureTimeNs) && params.exposureTimeNs > 0) {
|
|
503
|
+
payload.exposureTimeNs = params.exposureTimeNs
|
|
504
|
+
}
|
|
505
|
+
return payload
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
const nativeRequestPhoto = NativeBluetoothSdkModule.requestPhoto.bind(NativeBluetoothSdkModule)
|
|
509
|
+
NativeBluetoothSdkModule.requestPhoto = function (params: PhotoRequestParams) {
|
|
510
|
+
return nativeRequestPhoto(photoRequestParamsForNative(params) as unknown as PhotoRequestParams)
|
|
511
|
+
}
|
|
512
|
+
|
|
490
513
|
export default NativeBluetoothSdkModule
|
|
491
514
|
export const BluetoothSdk = NativeBluetoothSdkModule as BluetoothSdkPublicModule
|