@mentra/bluetooth-sdk 0.1.20 → 0.1.21-beta.0
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 +5 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +127 -24
- package/android/src/main/java/com/mentra/bluetoothsdk/Bridge.kt +65 -14
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +46 -3
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +369 -97
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraLocalNetworkModule.kt +333 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/camera/CameraModels.kt +3 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G1.kt +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G2.kt +16 -9
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Mach1.kt +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.kt +183 -30
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraNex.kt +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Nimo.kt +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/SGCManager.kt +4 -2
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Simulated.kt +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/status/WifiHotspotStatus.kt +19 -3
- package/android/src/main/java/com/mentra/bluetoothsdk/streaming/StreamModels.kt +15 -2
- package/android/src/main/java/com/mentra/bluetoothsdk/tts/TTSTools.kt +115 -47
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/audio/PcmStreamPlayer.kt +328 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/camera/PhotoRequestTest.kt +15 -0
- package/build/BluetoothSdk.types.d.ts +46 -1
- package/build/BluetoothSdk.types.d.ts.map +1 -1
- package/build/BluetoothSdk.types.js.map +1 -1
- package/build/_internal.d.ts +2 -0
- package/build/_internal.d.ts.map +1 -1
- package/build/_internal.js +2 -0
- package/build/_internal.js.map +1 -1
- package/build/_private/BluetoothSdkModule.d.ts +23 -2
- package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
- package/build/_private/BluetoothSdkModule.js +20 -0
- package/build/_private/BluetoothSdkModule.js.map +1 -1
- package/build/_private/MentraLocalNetworkModule.d.ts +37 -0
- package/build/_private/MentraLocalNetworkModule.d.ts.map +1 -0
- package/build/_private/MentraLocalNetworkModule.js +3 -0
- package/build/_private/MentraLocalNetworkModule.js.map +1 -0
- package/build/_private/cameraRequestPayload.d.ts.map +1 -1
- package/build/_private/cameraRequestPayload.js +1 -0
- package/build/_private/cameraRequestPayload.js.map +1 -1
- package/build/_private/photoRequestPayload.d.ts.map +1 -1
- package/build/_private/photoRequestPayload.js +1 -0
- package/build/_private/photoRequestPayload.js.map +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +4 -0
- package/build/index.js.map +1 -1
- package/expo-module.config.json +1 -0
- package/ios/BluetoothSdkModule.swift +72 -0
- package/ios/Source/BluetoothSdkDefaults.swift +1 -1
- package/ios/Source/Bridge.swift +72 -18
- package/ios/Source/DeviceManager.swift +51 -3
- package/ios/Source/DeviceStore.swift +1 -1
- package/ios/Source/MentraBluetoothSDK.swift +289 -25
- package/ios/Source/audio/PcmStreamPlayer.swift +410 -0
- package/ios/Source/camera/CameraModels.swift +8 -3
- package/ios/Source/sgcs/G1.swift +1 -1
- package/ios/Source/sgcs/G2.swift +19 -7
- package/ios/Source/sgcs/Mach1.swift +1 -1
- package/ios/Source/sgcs/MentraLive.swift +318 -37
- package/ios/Source/sgcs/MentraNex.swift +1 -1
- package/ios/Source/sgcs/Nimo.swift +1 -1
- package/ios/Source/sgcs/SGCManager.swift +5 -2
- package/ios/Source/sgcs/Simulated.swift +1 -1
- package/ios/Source/status/WifiHotspotStatus.swift +21 -2
- package/ios/Source/streaming/StreamModels.swift +9 -0
- package/ios/Source/tts/TTSTools.swift +1 -1
- package/package.json +1 -2
- package/src/BluetoothSdk.types.ts +49 -0
- package/src/_internal.ts +2 -0
- package/src/_private/BluetoothSdkModule.ts +64 -13
- package/src/_private/MentraLocalNetworkModule.ts +50 -0
- package/src/_private/cameraRequestPayload.ts +1 -0
- package/src/_private/photoRequestPayload.ts +2 -3
- package/src/index.ts +4 -0
package/ios/Source/Bridge.swift
CHANGED
|
@@ -400,7 +400,10 @@ class Bridge {
|
|
|
400
400
|
Bridge.sendTypedMessage("glasses_serial_number", body: body)
|
|
401
401
|
}
|
|
402
402
|
|
|
403
|
-
|
|
403
|
+
/// `error` is the glasses' provisioning failure reason (e.g. "connect_timeout",
|
|
404
|
+
/// "connected_to_other_network") when this status is the verdict of a failed
|
|
405
|
+
/// connect attempt; nil for routine link-state updates.
|
|
406
|
+
static func sendWifiStatusChange(connected: Bool, ssid: String?, localIp: String?, error: String? = nil) {
|
|
404
407
|
guard let status = WifiStatus.fromStoreFields(
|
|
405
408
|
connected: connected,
|
|
406
409
|
ssid: ssid,
|
|
@@ -408,27 +411,78 @@ class Bridge {
|
|
|
408
411
|
) else {
|
|
409
412
|
return
|
|
410
413
|
}
|
|
411
|
-
|
|
414
|
+
var body = status.values
|
|
415
|
+
if let error {
|
|
416
|
+
body["error"] = error
|
|
417
|
+
}
|
|
418
|
+
Bridge.sendTypedMessage("wifi_status_change", body: body)
|
|
412
419
|
}
|
|
413
420
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
421
|
+
/// Claim the WiFi scan-results store for a newly requested scan. Called by the
|
|
422
|
+
/// SDK when it generates the scanId, BEFORE the scan command goes out: store
|
|
423
|
+
/// ownership is decided at request time, not by whichever chunk arrives first,
|
|
424
|
+
/// so a delayed chunk from an older, abandoned scan can never reset or clobber
|
|
425
|
+
/// the current scan's accumulator.
|
|
426
|
+
@MainActor
|
|
427
|
+
static func claimWifiScanResults(scanId: String) {
|
|
428
|
+
DeviceStore.shared.apply("bluetooth", "wifiScanActiveScanId", scanId)
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
static func updateWifiScanResults(
|
|
432
|
+
_ networks: [[String: Any]],
|
|
433
|
+
scanComplete: Bool,
|
|
434
|
+
scanId: String? = nil
|
|
435
|
+
) {
|
|
436
|
+
// Correlated scans accumulate chunks until the terminal scan_complete, so
|
|
437
|
+
// chunks must reach the SDK in receive order. A Task per message can reach
|
|
438
|
+
// the MainActor out of creation order; DispatchQueue.main keeps the FIFO
|
|
439
|
+
// order of the serial bluetooth queue that delivers these.
|
|
440
|
+
DispatchQueue.main.async {
|
|
441
|
+
MainActor.assumeIsolated {
|
|
442
|
+
// Only chunks echoing the active scanId claimed at request time may
|
|
443
|
+
// mutate the store; foreign chunks are still forwarded to the SDK
|
|
444
|
+
// sink, which drops stale ids itself. Scan-id-less chunks (old
|
|
445
|
+
// firmware) keep the legacy accumulate-forever store behavior.
|
|
446
|
+
let ownsStore: Bool
|
|
447
|
+
if let scanId {
|
|
448
|
+
ownsStore =
|
|
449
|
+
scanId == DeviceStore.shared.get("bluetooth", "wifiScanActiveScanId") as? String
|
|
450
|
+
} else {
|
|
451
|
+
ownsStore = true
|
|
452
|
+
}
|
|
453
|
+
var updatedNetworks: [[String: Any]] = networks
|
|
454
|
+
if ownsStore {
|
|
455
|
+
var storedNetworks: [[String: Any]] =
|
|
456
|
+
DeviceStore.shared.get("bluetooth", "wifiScanResults") as? [[String: Any]] ?? []
|
|
457
|
+
if let scanId,
|
|
458
|
+
scanId != DeviceStore.shared.get("bluetooth", "wifiScanResultsScanId") as? String
|
|
459
|
+
{
|
|
460
|
+
// First chunk of a new scan: drop networks accumulated for a previous scan
|
|
461
|
+
// so stale entries never carry over into this scan's store.
|
|
462
|
+
storedNetworks = []
|
|
463
|
+
DeviceStore.shared.apply("bluetooth", "wifiScanResultsScanId", scanId)
|
|
464
|
+
}
|
|
465
|
+
// add the networks to the storedNetworks array, removing duplicates by ssid
|
|
466
|
+
for network in networks {
|
|
467
|
+
if !storedNetworks.contains(where: {
|
|
468
|
+
$0["ssid"] as? String == network["ssid"] as? String
|
|
469
|
+
}) {
|
|
470
|
+
storedNetworks.append(network)
|
|
471
|
+
}
|
|
425
472
|
}
|
|
473
|
+
DeviceStore.shared.apply("bluetooth", "wifiScanResults", storedNetworks)
|
|
474
|
+
updatedNetworks = storedNetworks
|
|
426
475
|
}
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
476
|
+
// Correlated scans: the SDK accumulates and dedupes chunks per scanId itself,
|
|
477
|
+
// so forward only this chunk; the merged store list is for UI consumers.
|
|
478
|
+
var body: [String: Any] = [
|
|
479
|
+
"networks": scanId != nil ? networks : updatedNetworks,
|
|
480
|
+
"scanComplete": scanComplete,
|
|
481
|
+
]
|
|
482
|
+
if let scanId {
|
|
483
|
+
body["scanId"] = scanId
|
|
484
|
+
}
|
|
485
|
+
Bridge.sendTypedMessage("wifi_scan_result", body: body)
|
|
432
486
|
}
|
|
433
487
|
}
|
|
434
488
|
}
|
|
@@ -799,6 +799,11 @@ struct ViewState {
|
|
|
799
799
|
return
|
|
800
800
|
}
|
|
801
801
|
|
|
802
|
+
if sgc?.isMicSuspendedForAudio == true {
|
|
803
|
+
Bridge.log("MAN: Glasses mic intentionally suspended for phone audio; skipping mic recovery")
|
|
804
|
+
return
|
|
805
|
+
}
|
|
806
|
+
|
|
802
807
|
let timeSinceLastLc3Event = Date().timeIntervalSince(lastLc3Event ?? Date())
|
|
803
808
|
if timeSinceLastLc3Event > 5 {
|
|
804
809
|
Bridge.log("MAN: No audio activity in the last 5 seconds from glasses, reinitializing glasses mic")
|
|
@@ -1276,10 +1281,10 @@ struct ViewState {
|
|
|
1276
1281
|
sgc?.sendStreamKeepAlive(message)
|
|
1277
1282
|
}
|
|
1278
1283
|
|
|
1279
|
-
func requestWifiScan() {
|
|
1284
|
+
func requestWifiScan(scanId: String? = nil) {
|
|
1280
1285
|
Bridge.log("MAN: Requesting wifi scan")
|
|
1281
1286
|
DeviceStore.shared.apply("bluetooth", "wifiScanResults", [])
|
|
1282
|
-
sgc?.requestWifiScan()
|
|
1287
|
+
sgc?.requestWifiScan(scanId: scanId)
|
|
1283
1288
|
}
|
|
1284
1289
|
|
|
1285
1290
|
func sendIncidentId(_ incidentId: String, apiBaseUrl: String? = nil) {
|
|
@@ -1359,6 +1364,42 @@ struct ViewState {
|
|
|
1359
1364
|
try liveSgc().sendCameraFovSetting(requestId: requestId, fov: fov, roiPosition: roiPosition)
|
|
1360
1365
|
}
|
|
1361
1366
|
|
|
1367
|
+
func sendLegacyCameraFovSetting(fov: Int, roiPosition: Int) throws {
|
|
1368
|
+
let glassesConnected = DeviceStore.shared.get("glasses", "connected") as? Bool ?? false
|
|
1369
|
+
guard glassesConnected else {
|
|
1370
|
+
throw BluetoothSdkError(code: "not_connected", message: "Mentra Live glasses are not connected.")
|
|
1371
|
+
}
|
|
1372
|
+
try liveSgc().sendCameraFovSetting(requestId: nil, fov: fov, roiPosition: roiPosition)
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
func restoreLegacyCameraFovSetting() throws {
|
|
1376
|
+
let glassesConnected = DeviceStore.shared.get("glasses", "connected") as? Bool ?? false
|
|
1377
|
+
guard glassesConnected else {
|
|
1378
|
+
throw BluetoothSdkError(code: "not_connected", message: "Mentra Live glasses are not connected.")
|
|
1379
|
+
}
|
|
1380
|
+
try liveSgc().sendCameraFovSetting()
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
func sendCameraFovOverride(
|
|
1384
|
+
requestId: String,
|
|
1385
|
+
leaseId: String,
|
|
1386
|
+
fov: Int,
|
|
1387
|
+
roiPosition: Int,
|
|
1388
|
+
ttlMs: Int
|
|
1389
|
+
) throws {
|
|
1390
|
+
try liveSgc().sendCameraFovOverride(
|
|
1391
|
+
requestId: requestId,
|
|
1392
|
+
leaseId: leaseId,
|
|
1393
|
+
fov: fov,
|
|
1394
|
+
roiPosition: roiPosition,
|
|
1395
|
+
ttlMs: ttlMs
|
|
1396
|
+
)
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
func releaseCameraFovOverride(requestId: String, leaseId: String) throws {
|
|
1400
|
+
try liveSgc().releaseCameraFovOverride(requestId: requestId, leaseId: leaseId)
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1362
1403
|
func sendCameraTuningConfig(requestId: String, anrOn: Bool, gainOn: Bool) throws {
|
|
1363
1404
|
try liveSgc().sendCameraTuningConfig(requestId: requestId, anrOn: anrOn, gainOn: gainOn)
|
|
1364
1405
|
}
|
|
@@ -1366,6 +1407,7 @@ struct ViewState {
|
|
|
1366
1407
|
func warmUpCamera(
|
|
1367
1408
|
requestId: String,
|
|
1368
1409
|
size: PhotoSize,
|
|
1410
|
+
mode: PhotoMode = .photo,
|
|
1369
1411
|
exposureTimeNs: Double?,
|
|
1370
1412
|
durationMs: Int
|
|
1371
1413
|
) throws {
|
|
@@ -1378,11 +1420,16 @@ struct ViewState {
|
|
|
1378
1420
|
live.warmUpCamera(
|
|
1379
1421
|
requestId: requestId,
|
|
1380
1422
|
size: size,
|
|
1423
|
+
mode: mode,
|
|
1381
1424
|
exposureTimeNs: exposureTimeNs,
|
|
1382
1425
|
durationMs: durationMs
|
|
1383
1426
|
)
|
|
1384
1427
|
}
|
|
1385
1428
|
|
|
1429
|
+
func stopCameraWarmUp(requestId: String) throws {
|
|
1430
|
+
try liveSgc().stopCameraWarmUp(requestId: requestId)
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1386
1433
|
/// Request version info from glasses.
|
|
1387
1434
|
/// Glasses will respond with version_info message containing build number, firmware version, etc.
|
|
1388
1435
|
func requestVersionInfo() {
|
|
@@ -1506,7 +1553,8 @@ struct ViewState {
|
|
|
1506
1553
|
zsl: request.zsl,
|
|
1507
1554
|
ispDigitalGain: request.ispDigitalGain,
|
|
1508
1555
|
ispAnalogGain: request.ispAnalogGain,
|
|
1509
|
-
mode: request.mode
|
|
1556
|
+
mode: request.mode,
|
|
1557
|
+
transferMethod: request.transferMethod
|
|
1510
1558
|
)
|
|
1511
1559
|
Bridge.log(
|
|
1512
1560
|
"MAN: PHOTO PIPELINE [4/6] DeviceManager.requestPhoto requestId=\(routed.requestId) webhookUrl=\(routed.webhookUrl ?? "nil") size=\(routed.size.rawValue) compress=\(routed.compress?.rawValue ?? "none") save=\(routed.save) sound=\(routed.sound) exposureTimeNs=\(manualExposureNs.map { String($0) } ?? "nil") iso=\(manualIso.map { String($0) } ?? "auto") aeDivisor=\(routed.aeExposureDivisor.map { String($0) } ?? "nil") isoCap=\(routed.isoCap.map { String($0) } ?? "nil") sgc=\(sgc != nil ? String(describing: type(of: sgc!)) : "null")"
|
|
@@ -93,7 +93,7 @@ class DeviceStore {
|
|
|
93
93
|
store.set("bluetooth", "screen_disabled", false)
|
|
94
94
|
store.set("bluetooth", "button_photo_size", "max")
|
|
95
95
|
store.set("bluetooth", "button_max_recording_time", 10)
|
|
96
|
-
store.set("bluetooth", "camera_fov", ["fov":
|
|
96
|
+
store.set("bluetooth", "camera_fov", ["fov": 102, "roi_position": 0])
|
|
97
97
|
store.set("bluetooth", "button_video_width", 1280)
|
|
98
98
|
store.set("bluetooth", "button_video_height", 720)
|
|
99
99
|
store.set("bluetooth", "button_video_fps", 30)
|