@mentra/bluetooth-sdk 0.1.12 → 0.1.13
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 -9
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +51 -3
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +73 -21
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +190 -26
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdkDebug.kt +14 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/ObservableStore.kt +20 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/OtaManifest.kt +153 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/camera/CameraModels.kt +78 -10
- package/android/src/main/java/com/mentra/bluetoothsdk/controllers/ControllerManager.kt +3 -14
- package/android/src/main/java/com/mentra/bluetoothsdk/controllers/R1.kt +3 -6
- package/android/src/main/java/com/mentra/bluetoothsdk/services/PhoneMic.kt +90 -4
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G1.kt +3835 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G2.kt +20 -23
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/{Mach1.java → Mach1.kt} +517 -560
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.kt +8712 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraNex.kt +11 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/SGCManager.kt +3 -13
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Simulated.kt +7 -14
- package/build/BluetoothSdk.types.d.ts +39 -9
- package/build/BluetoothSdk.types.d.ts.map +1 -1
- package/build/BluetoothSdk.types.js.map +1 -1
- package/build/_private/BluetoothSdkModule.d.ts +8 -6
- package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
- package/build/_private/BluetoothSdkModule.js +0 -2
- package/build/_private/BluetoothSdkModule.js.map +1 -1
- package/build/_private/photoRequestPayload.d.ts +3 -1
- package/build/_private/photoRequestPayload.d.ts.map +1 -1
- package/build/_private/photoRequestPayload.js +43 -1
- package/build/_private/photoRequestPayload.js.map +1 -1
- package/build/debug.d.ts +3 -0
- package/build/debug.d.ts.map +1 -0
- package/build/debug.js +8 -0
- package/build/debug.js.map +1 -0
- package/build/index.d.ts +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +14 -5
- package/build/index.js.map +1 -1
- package/ios/BluetoothSdkModule.swift +38 -57
- package/ios/Source/BluetoothSdkDefaults.swift +23 -3
- package/ios/Source/DeviceManager.swift +37 -26
- package/ios/Source/DeviceStore.swift +5 -1
- package/ios/Source/MentraBluetoothSDK.swift +197 -26
- package/ios/Source/MentraBluetoothSDKDebug.swift +12 -0
- package/ios/Source/ObservableStore.swift +11 -0
- package/ios/Source/OtaManifest.swift +170 -0
- package/ios/Source/camera/CameraModels.swift +218 -8
- package/ios/Source/controllers/ControllerManager.swift +2 -5
- package/ios/Source/controllers/R1.swift +2 -5
- package/ios/Source/sgcs/G1.swift +6 -5
- package/ios/Source/sgcs/G2.swift +20 -18
- package/ios/Source/sgcs/Mach1.swift +6 -5
- package/ios/Source/sgcs/MentraLive.swift +129 -33
- package/ios/Source/sgcs/MentraNex.swift +6 -5
- package/ios/Source/sgcs/SGCManager.swift +3 -5
- package/ios/Source/sgcs/Simulated.swift +7 -3
- package/ios/Source/status/DeviceStatus.swift +1 -1
- package/package.json +6 -1
- package/src/BluetoothSdk.types.ts +40 -9
- package/src/_private/BluetoothSdkModule.ts +8 -9
- package/src/_private/photoRequestPayload.ts +45 -2
- package/src/debug.ts +9 -0
- package/src/index.ts +23 -6
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G1.java +0 -3974
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.java +0 -7434
|
@@ -1234,6 +1234,9 @@ class MentraLive: NSObject, SGCManager {
|
|
|
1234
1234
|
func getBatteryStatus() {}
|
|
1235
1235
|
func setBrightness(_: Int, autoMode _: Bool) {}
|
|
1236
1236
|
func clearDisplay() {}
|
|
1237
|
+
func sendText(_ text: String) async {
|
|
1238
|
+
await sendTextWall(text)
|
|
1239
|
+
}
|
|
1237
1240
|
func sendTextWall(_: String) async {}
|
|
1238
1241
|
func ping() {
|
|
1239
1242
|
Bridge.log("LIVE: ping()")
|
|
@@ -1611,18 +1614,15 @@ class MentraLive: NSObject, SGCManager {
|
|
|
1611
1614
|
}
|
|
1612
1615
|
}
|
|
1613
1616
|
|
|
1614
|
-
func requestPhoto(
|
|
1615
|
-
_ requestId: String, appId: String, size: String?, webhookUrl: String?, authToken: String?,
|
|
1616
|
-
compress: String?, flash: Bool, save: Bool, sound: Bool, exposureTimeNs: Double?, iso: Int?
|
|
1617
|
-
) {
|
|
1617
|
+
func requestPhoto(_ request: PhotoRequest) {
|
|
1618
1618
|
Bridge.log(
|
|
1619
|
-
"LIVE: PHOTO PIPELINE [5/6] requestPhoto() entry requestId=\(requestId) appId=\(appId) flash=\(flash) save=\(save) sound=\(sound) iso=\(iso.map { String($0) } ?? "auto")"
|
|
1619
|
+
"LIVE: PHOTO PIPELINE [5/6] requestPhoto() entry requestId=\(request.requestId) appId=\(request.appId) flash=\(request.flash) save=\(request.save) sound=\(request.sound) iso=\(request.iso.map { String($0) } ?? "auto") aeDivisor=\(request.aeExposureDivisor.map { String($0) } ?? "nil")"
|
|
1620
1620
|
)
|
|
1621
1621
|
|
|
1622
1622
|
var json: [String: Any] = [
|
|
1623
1623
|
"type": "take_photo",
|
|
1624
|
-
"requestId": requestId,
|
|
1625
|
-
"appId": appId,
|
|
1624
|
+
"requestId": request.requestId,
|
|
1625
|
+
"appId": request.appId,
|
|
1626
1626
|
]
|
|
1627
1627
|
|
|
1628
1628
|
// Always generate BLE ID for potential fallback
|
|
@@ -1631,15 +1631,15 @@ class MentraLive: NSObject, SGCManager {
|
|
|
1631
1631
|
json["bleImgId"] = bleImgId
|
|
1632
1632
|
json["transferMethod"] = "auto"
|
|
1633
1633
|
|
|
1634
|
-
if let webhookUrl, !webhookUrl.isEmpty {
|
|
1634
|
+
if let webhookUrl = request.webhookUrl, !webhookUrl.isEmpty {
|
|
1635
1635
|
json["webhookUrl"] = webhookUrl
|
|
1636
1636
|
|
|
1637
1637
|
var transfer = BlePhotoTransfer(
|
|
1638
|
-
bleImgId: bleImgId, requestId: requestId, webhookUrl: webhookUrl
|
|
1638
|
+
bleImgId: bleImgId, requestId: request.requestId, webhookUrl: webhookUrl
|
|
1639
1639
|
)
|
|
1640
1640
|
|
|
1641
1641
|
// Store authToken for BLE transfer if provided
|
|
1642
|
-
if let authToken, !authToken.isEmpty {
|
|
1642
|
+
if let authToken = request.authToken, !authToken.isEmpty {
|
|
1643
1643
|
transfer.authToken = authToken
|
|
1644
1644
|
}
|
|
1645
1645
|
|
|
@@ -1647,32 +1647,28 @@ class MentraLive: NSObject, SGCManager {
|
|
|
1647
1647
|
}
|
|
1648
1648
|
|
|
1649
1649
|
// Add authToken to JSON if provided
|
|
1650
|
-
if let authToken, !authToken.isEmpty {
|
|
1650
|
+
if let authToken = request.authToken, !authToken.isEmpty {
|
|
1651
1651
|
json["authToken"] = authToken
|
|
1652
1652
|
}
|
|
1653
1653
|
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
} else {
|
|
1658
|
-
json["size"] = "medium"
|
|
1659
|
-
}
|
|
1660
|
-
|
|
1661
|
-
// Add compress parameter
|
|
1662
|
-
json["compress"] = compress ?? "none"
|
|
1654
|
+
let allowedSizes = ["low", "medium", "high", "max"]
|
|
1655
|
+
let size = request.size.rawValue
|
|
1656
|
+
json["size"] = allowedSizes.contains(size) ? size : "medium"
|
|
1663
1657
|
|
|
1664
|
-
json["
|
|
1665
|
-
json["
|
|
1666
|
-
json["
|
|
1658
|
+
json["compress"] = request.compress?.rawValue ?? "none"
|
|
1659
|
+
json["flash"] = request.flash
|
|
1660
|
+
json["save"] = request.save
|
|
1661
|
+
json["sound"] = request.sound
|
|
1667
1662
|
|
|
1668
|
-
if let e = exposureTimeNs, e.isFinite, e > 0, e <= Double(Int64.max) {
|
|
1669
|
-
Bridge.log("LIVE: Using manual exposure time for photo request \(requestId): \(Int64(e)) ns")
|
|
1663
|
+
if let e = request.exposureTimeNs, e.isFinite, e > 0, e <= Double(Int64.max) {
|
|
1664
|
+
Bridge.log("LIVE: Using manual exposure time for photo request \(request.requestId): \(Int64(e)) ns")
|
|
1670
1665
|
json["exposureTimeNs"] = Int64(e)
|
|
1671
1666
|
}
|
|
1672
|
-
if let iso, iso > 0 {
|
|
1673
|
-
Bridge.log("LIVE: Using manual ISO for photo request \(requestId): ISO \(iso)")
|
|
1667
|
+
if let iso = request.iso, iso > 0 {
|
|
1668
|
+
Bridge.log("LIVE: Using manual ISO for photo request \(request.requestId): ISO \(iso)")
|
|
1674
1669
|
json["iso"] = iso
|
|
1675
1670
|
}
|
|
1671
|
+
request.appendScanFields(to: &json)
|
|
1676
1672
|
|
|
1677
1673
|
Bridge.log("LIVE: PHOTO PIPELINE [5b/6] take_photo JSON ready bleImgId=\(bleImgId) transferMethod=auto")
|
|
1678
1674
|
Bridge.log("LIVE: PHOTO PIPELINE [6/6] Dispatching take_photo to sendJson()")
|
|
@@ -2883,13 +2879,17 @@ class MentraLive: NSObject, SGCManager {
|
|
|
2883
2879
|
/// Send OTA start command to glasses.
|
|
2884
2880
|
/// Called when user approves an update (onboarding or background mode).
|
|
2885
2881
|
/// Triggers glasses to begin download and installation.
|
|
2886
|
-
func sendOtaStart() {
|
|
2882
|
+
func sendOtaStart(otaVersionUrl: String?) {
|
|
2887
2883
|
Bridge.log("LIVE: 📱 Sending ota_start command to glasses")
|
|
2888
2884
|
|
|
2889
|
-
|
|
2885
|
+
var json: [String: Any] = [
|
|
2890
2886
|
"type": "ota_start",
|
|
2891
2887
|
"timestamp": Int(Date().timeIntervalSince1970 * 1000),
|
|
2892
2888
|
]
|
|
2889
|
+
if let otaVersionUrl = otaVersionUrl?.trimmingCharacters(in: .whitespacesAndNewlines),
|
|
2890
|
+
!otaVersionUrl.isEmpty {
|
|
2891
|
+
json["ota_version_url"] = otaVersionUrl
|
|
2892
|
+
}
|
|
2893
2893
|
|
|
2894
2894
|
sendJson(json, wakeUp: true)
|
|
2895
2895
|
}
|
|
@@ -5197,12 +5197,73 @@ extension MentraLive {
|
|
|
5197
5197
|
}
|
|
5198
5198
|
|
|
5199
5199
|
func sendButtonPhotoSettings() {
|
|
5200
|
-
let size = DeviceStore.shared.get("bluetooth", "button_photo_size") as
|
|
5201
|
-
|
|
5200
|
+
let size = DeviceStore.shared.get("bluetooth", "button_photo_size") as? String
|
|
5201
|
+
let mfnr = DeviceStore.shared.get("bluetooth", "button_photo_mfnr") as? Bool
|
|
5202
|
+
let zsl = DeviceStore.shared.get("bluetooth", "button_photo_zsl") as? Bool
|
|
5203
|
+
let noiseReduction = DeviceStore.shared.get("bluetooth", "button_photo_noise_reduction") as? Bool
|
|
5204
|
+
let edgeEnhancement = DeviceStore.shared.get("bluetooth", "button_photo_edge_enhancement") as? Bool
|
|
5205
|
+
let ispDigitalGain = DeviceStore.shared.get("bluetooth", "button_photo_isp_digital_gain") as? Int
|
|
5206
|
+
let ispAnalogGain = DeviceStore.shared.get("bluetooth", "button_photo_isp_analog_gain") as? String
|
|
5207
|
+
let aeExposureDivisor = DeviceStore.shared.get("bluetooth", "button_photo_ae_exposure_divisor") as? Int
|
|
5208
|
+
let isoCap = DeviceStore.shared.get("bluetooth", "button_photo_iso_cap") as? Int
|
|
5209
|
+
let compressStr = DeviceStore.shared.get("bluetooth", "button_photo_compress") as? String
|
|
5210
|
+
let sound = DeviceStore.shared.get("bluetooth", "button_photo_sound") as? Bool
|
|
5211
|
+
|
|
5212
|
+
let settings = ButtonPhotoSettings(
|
|
5213
|
+
size: ButtonPhotoSize(normalizedRawValue: size ?? "medium"),
|
|
5214
|
+
mfnr: mfnr,
|
|
5215
|
+
zsl: zsl,
|
|
5216
|
+
noiseReduction: noiseReduction,
|
|
5217
|
+
edgeEnhancement: edgeEnhancement,
|
|
5218
|
+
ispDigitalGain: ispDigitalGain,
|
|
5219
|
+
ispAnalogGain: ispAnalogGain,
|
|
5220
|
+
aeExposureDivisor: aeExposureDivisor,
|
|
5221
|
+
isoCap: isoCap,
|
|
5222
|
+
compress: compressStr,
|
|
5223
|
+
sound: sound,
|
|
5224
|
+
resetCaptureTuning: false
|
|
5225
|
+
)
|
|
5226
|
+
|
|
5227
|
+
sendButtonPhotoSettings(requestId: nil, settings: settings)
|
|
5202
5228
|
}
|
|
5203
5229
|
|
|
5204
5230
|
func sendButtonPhotoSettings(requestId: String?, size: String) {
|
|
5205
|
-
|
|
5231
|
+
sendButtonPhotoSettings(requestId: requestId, settings: ButtonPhotoSettings(size: ButtonPhotoSize(normalizedRawValue: size)))
|
|
5232
|
+
}
|
|
5233
|
+
|
|
5234
|
+
func sendButtonPhotoSettings(requestId: String?, settings: ButtonPhotoSettings) {
|
|
5235
|
+
var details = settings.size.map { "size=\($0.rawValue)" } ?? "size=unchanged"
|
|
5236
|
+
if let mfnr = settings.mfnr {
|
|
5237
|
+
details += ", mfnr=\(mfnr)"
|
|
5238
|
+
}
|
|
5239
|
+
if let zsl = settings.zsl {
|
|
5240
|
+
details += ", zsl=\(zsl)"
|
|
5241
|
+
}
|
|
5242
|
+
if let noiseReduction = settings.noiseReduction {
|
|
5243
|
+
details += ", noiseReduction=\(noiseReduction)"
|
|
5244
|
+
}
|
|
5245
|
+
if let edgeEnhancement = settings.edgeEnhancement {
|
|
5246
|
+
details += ", edgeEnhancement=\(edgeEnhancement)"
|
|
5247
|
+
}
|
|
5248
|
+
if let ispDigitalGain = settings.ispDigitalGain {
|
|
5249
|
+
details += ", ispDigitalGain=\(ispDigitalGain)"
|
|
5250
|
+
}
|
|
5251
|
+
if let ispAnalogGain = settings.ispAnalogGain {
|
|
5252
|
+
details += ", ispAnalogGain=\(ispAnalogGain)"
|
|
5253
|
+
}
|
|
5254
|
+
if let aeExposureDivisor = settings.aeExposureDivisor {
|
|
5255
|
+
details += ", aeExposureDivisor=\(aeExposureDivisor)"
|
|
5256
|
+
}
|
|
5257
|
+
if let isoCap = settings.isoCap {
|
|
5258
|
+
details += ", isoCap=\(isoCap)"
|
|
5259
|
+
}
|
|
5260
|
+
if let compress = settings.compress {
|
|
5261
|
+
details += ", compress=\(compress)"
|
|
5262
|
+
}
|
|
5263
|
+
if let sound = settings.sound {
|
|
5264
|
+
details += ", sound=\(sound)"
|
|
5265
|
+
}
|
|
5266
|
+
Bridge.log("Sending button photo setting: \(details)")
|
|
5206
5267
|
|
|
5207
5268
|
guard connectionState == ConnTypes.CONNECTED else {
|
|
5208
5269
|
Bridge.log("Cannot send button photo settings - not connected")
|
|
@@ -5211,11 +5272,46 @@ extension MentraLive {
|
|
|
5211
5272
|
|
|
5212
5273
|
var json: [String: Any] = [
|
|
5213
5274
|
"type": "button_photo_setting",
|
|
5214
|
-
"size": size,
|
|
5215
5275
|
]
|
|
5276
|
+
if let size = settings.size {
|
|
5277
|
+
json["size"] = size.rawValue
|
|
5278
|
+
}
|
|
5216
5279
|
if let requestId, !requestId.isEmpty {
|
|
5217
5280
|
json["request_id"] = requestId
|
|
5218
5281
|
}
|
|
5282
|
+
if let mfnr = settings.mfnr {
|
|
5283
|
+
json["mfnr"] = mfnr
|
|
5284
|
+
}
|
|
5285
|
+
if let zsl = settings.zsl {
|
|
5286
|
+
json["zsl"] = zsl
|
|
5287
|
+
}
|
|
5288
|
+
if let noiseReduction = settings.noiseReduction {
|
|
5289
|
+
json["noiseReduction"] = noiseReduction
|
|
5290
|
+
}
|
|
5291
|
+
if let edgeEnhancement = settings.edgeEnhancement {
|
|
5292
|
+
json["edgeEnhancement"] = edgeEnhancement
|
|
5293
|
+
}
|
|
5294
|
+
if let ispDigitalGain = settings.ispDigitalGain {
|
|
5295
|
+
json["ispDigitalGain"] = ispDigitalGain
|
|
5296
|
+
}
|
|
5297
|
+
if let ispAnalogGain = settings.ispAnalogGain, !ispAnalogGain.isEmpty {
|
|
5298
|
+
json["ispAnalogGain"] = ispAnalogGain
|
|
5299
|
+
}
|
|
5300
|
+
if let aeExposureDivisor = settings.aeExposureDivisor, aeExposureDivisor > 1 {
|
|
5301
|
+
json["aeExposureDivisor"] = aeExposureDivisor
|
|
5302
|
+
}
|
|
5303
|
+
if let isoCap = settings.isoCap, isoCap > 0 {
|
|
5304
|
+
json["isoCap"] = isoCap
|
|
5305
|
+
}
|
|
5306
|
+
if let compress = settings.compress, !compress.isEmpty {
|
|
5307
|
+
json["compress"] = compress
|
|
5308
|
+
}
|
|
5309
|
+
if let sound = settings.sound {
|
|
5310
|
+
json["sound"] = sound
|
|
5311
|
+
}
|
|
5312
|
+
if settings.resetCaptureTuning == true {
|
|
5313
|
+
json["resetCaptureTuning"] = true
|
|
5314
|
+
}
|
|
5219
5315
|
sendJson(json, wakeUp: true)
|
|
5220
5316
|
}
|
|
5221
5317
|
|
|
@@ -51,10 +51,7 @@ class MentraNexSGC: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate, SG
|
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
func requestPhoto(
|
|
55
|
-
_: String, appId _: String, size _: String?, webhookUrl _: String?, authToken _: String?,
|
|
56
|
-
compress _: String?, flash _: Bool, save _: Bool, sound _: Bool, exposureTimeNs _: Double?, iso _: Int?
|
|
57
|
-
) {}
|
|
54
|
+
func requestPhoto(_: PhotoRequest) {}
|
|
58
55
|
|
|
59
56
|
func startStream(_: [String: Any]) {}
|
|
60
57
|
|
|
@@ -81,6 +78,10 @@ class MentraNexSGC: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate, SG
|
|
|
81
78
|
updateGlassesAutoBrightness(autoMode)
|
|
82
79
|
}
|
|
83
80
|
|
|
81
|
+
func sendText(_ text: String) async {
|
|
82
|
+
await sendTextWall(text)
|
|
83
|
+
}
|
|
84
|
+
|
|
84
85
|
func sendDoubleTextWall(_ top: String, _ bottom: String) async {
|
|
85
86
|
await sendTextWall("\(top)\n\(bottom)")
|
|
86
87
|
}
|
|
@@ -178,7 +179,7 @@ class MentraNexSGC: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate, SG
|
|
|
178
179
|
|
|
179
180
|
func sendHotspotState(_: Bool) {}
|
|
180
181
|
|
|
181
|
-
func sendOtaStart() {}
|
|
182
|
+
func sendOtaStart(otaVersionUrl: String?) {}
|
|
182
183
|
func sendOtaQueryStatus() {}
|
|
183
184
|
|
|
184
185
|
func sendUserEmailToGlasses(_: String) {}
|
|
@@ -18,10 +18,7 @@ protocol SGCManager {
|
|
|
18
18
|
|
|
19
19
|
// MARK: - Camera & Media
|
|
20
20
|
|
|
21
|
-
func requestPhoto(
|
|
22
|
-
_ requestId: String, appId: String, size: String?, webhookUrl: String?, authToken: String?,
|
|
23
|
-
compress: String?, flash: Bool, save: Bool, sound: Bool, exposureTimeNs: Double?, iso: Int?
|
|
24
|
-
)
|
|
21
|
+
func requestPhoto(_ request: PhotoRequest)
|
|
25
22
|
func startStream(_ message: [String: Any])
|
|
26
23
|
func stopStream()
|
|
27
24
|
func sendStreamKeepAlive(_ message: [String: Any])
|
|
@@ -54,6 +51,7 @@ protocol SGCManager {
|
|
|
54
51
|
|
|
55
52
|
func setBrightness(_ level: Int, autoMode: Bool)
|
|
56
53
|
func clearDisplay()
|
|
54
|
+
func sendText(_ text: String) async
|
|
57
55
|
func sendTextWall(_ text: String) async
|
|
58
56
|
func sendDoubleTextWall(_ top: String, _ bottom: String) async
|
|
59
57
|
/// Display a bitmap. Optional `x`/`y`/`width`/`height` position and size the target
|
|
@@ -118,7 +116,7 @@ protocol SGCManager {
|
|
|
118
116
|
func sendWifiCredentials(_ ssid: String, _ password: String)
|
|
119
117
|
func forgetWifiNetwork(_ ssid: String)
|
|
120
118
|
func sendHotspotState(_ enabled: Bool)
|
|
121
|
-
func sendOtaStart()
|
|
119
|
+
func sendOtaStart(otaVersionUrl: String?)
|
|
122
120
|
func sendOtaQueryStatus()
|
|
123
121
|
func sendSetSystemTime(_ timestampMs: Int64)
|
|
124
122
|
func sendOtaRetryVersionCheck()
|
|
@@ -79,8 +79,8 @@ class Simulated: SGCManager {
|
|
|
79
79
|
|
|
80
80
|
// MARK: - Camera & Media
|
|
81
81
|
|
|
82
|
-
func requestPhoto(_
|
|
83
|
-
Bridge.log("requestPhoto")
|
|
82
|
+
func requestPhoto(_ request: PhotoRequest) {
|
|
83
|
+
Bridge.log("requestPhoto flash=\(request.flash) save=\(request.save) sound=\(request.sound)")
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
func startStream(_: [String: Any]) {
|
|
@@ -133,6 +133,10 @@ class Simulated: SGCManager {
|
|
|
133
133
|
Bridge.log("clearDisplay")
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
+
func sendText(_ text: String) async {
|
|
137
|
+
await sendTextWall(text)
|
|
138
|
+
}
|
|
139
|
+
|
|
136
140
|
func sendTextWall(_: String) async {
|
|
137
141
|
Bridge.log("sendTextWall")
|
|
138
142
|
}
|
|
@@ -246,7 +250,7 @@ class Simulated: SGCManager {
|
|
|
246
250
|
Bridge.log("sendUserEmailToGlasses: \(email)")
|
|
247
251
|
}
|
|
248
252
|
|
|
249
|
-
func sendOtaStart() {
|
|
253
|
+
func sendOtaStart(otaVersionUrl: String?) {
|
|
250
254
|
Bridge.log("sendOtaStart")
|
|
251
255
|
}
|
|
252
256
|
|
|
@@ -921,7 +921,7 @@ struct BluetoothStatusUpdate: CustomStringConvertible {
|
|
|
921
921
|
}
|
|
922
922
|
|
|
923
923
|
var buttonPhotoSize: ButtonPhotoSize? {
|
|
924
|
-
optionalStringValue(values, "button_photo_size").
|
|
924
|
+
optionalStringValue(values, "button_photo_size").map { ButtonPhotoSize(normalizedRawValue: $0) }
|
|
925
925
|
}
|
|
926
926
|
|
|
927
927
|
var buttonCameraLed: Bool? {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mentra/bluetooth-sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.13",
|
|
4
4
|
"description": "SDK for communicating with smart glasses",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"react-native": "src/index.ts",
|
|
@@ -22,6 +22,11 @@
|
|
|
22
22
|
"react-native": "./src/photo-receiver/index.ts",
|
|
23
23
|
"default": "./build/photo-receiver/index.js"
|
|
24
24
|
},
|
|
25
|
+
"./debug": {
|
|
26
|
+
"types": "./build/debug.d.ts",
|
|
27
|
+
"react-native": "./src/debug.ts",
|
|
28
|
+
"default": "./build/debug.js"
|
|
29
|
+
},
|
|
25
30
|
"./_private/*": null,
|
|
26
31
|
"./build/*": null,
|
|
27
32
|
"./src/*": null,
|
|
@@ -248,6 +248,13 @@ export type PhotoCaptureMetadata = {
|
|
|
248
248
|
sensorTimestampNs?: number
|
|
249
249
|
totalLightProxy?: number
|
|
250
250
|
mfnrLikely?: boolean
|
|
251
|
+
mfnrApplied?: boolean
|
|
252
|
+
width?: number
|
|
253
|
+
height?: number
|
|
254
|
+
noiseReductionWarning?: "not_implemented" | string
|
|
255
|
+
ispDigitalGainWarning?: "not_implemented" | string
|
|
256
|
+
ispAnalogGainWarning?: "not_implemented" | string
|
|
257
|
+
[key: string]: unknown
|
|
251
258
|
}
|
|
252
259
|
|
|
253
260
|
export type PhotoStatusEvent = {
|
|
@@ -421,8 +428,24 @@ export type SettingsAckSuccessEvent = Omit<SettingsAckEvent, "status"> & {
|
|
|
421
428
|
|
|
422
429
|
export type RgbLedAction = "on" | "off"
|
|
423
430
|
export type RgbLedColor = "red" | "green" | "blue" | "orange" | "white"
|
|
424
|
-
export type PhotoSize = "
|
|
425
|
-
export type ButtonPhotoSize = "
|
|
431
|
+
export type PhotoSize = "low" | "medium" | "high" | "max"
|
|
432
|
+
export type ButtonPhotoSize = "low" | "medium" | "high" | "max"
|
|
433
|
+
|
|
434
|
+
export type ButtonPhotoSettings = {
|
|
435
|
+
size: ButtonPhotoSize
|
|
436
|
+
mfnr?: boolean
|
|
437
|
+
zsl?: boolean
|
|
438
|
+
noiseReduction?: boolean
|
|
439
|
+
edgeEnhancement?: boolean
|
|
440
|
+
ispDigitalGain?: number
|
|
441
|
+
ispAnalogGain?: string
|
|
442
|
+
aeExposureDivisor?: number
|
|
443
|
+
isoCap?: number
|
|
444
|
+
compress?: PhotoCompression
|
|
445
|
+
sound?: boolean
|
|
446
|
+
/** When true, clears stored NR/edge/ISP presets on the glasses before applying other fields. */
|
|
447
|
+
resetCaptureTuning?: boolean
|
|
448
|
+
}
|
|
426
449
|
export type PhotoCompression = "none" | "medium" | "heavy"
|
|
427
450
|
|
|
428
451
|
/**
|
|
@@ -511,6 +534,17 @@ export type PhotoRequestParams = {
|
|
|
511
534
|
exposureTimeNs?: number | null
|
|
512
535
|
/** Sensor ISO for this capture only. Only used when exposureTimeNs enables manual exposure. */
|
|
513
536
|
iso?: number | null
|
|
537
|
+
/** After AE convergence, divide metered exposure by this factor (scan mode). */
|
|
538
|
+
aeExposureDivisor?: number
|
|
539
|
+
/** Cap ISO after AE metering (scan mode). */
|
|
540
|
+
isoCap?: number
|
|
541
|
+
/** Requested on wire; glasses may log not_implemented. */
|
|
542
|
+
noiseReduction?: boolean
|
|
543
|
+
edgeEnhancement?: boolean
|
|
544
|
+
mfnr?: boolean
|
|
545
|
+
zsl?: boolean
|
|
546
|
+
ispDigitalGain?: number
|
|
547
|
+
ispAnalogGain?: string
|
|
514
548
|
}
|
|
515
549
|
|
|
516
550
|
export type StreamVideoConfig = {
|
|
@@ -859,7 +893,6 @@ export type BluetoothSdkEventMap = {
|
|
|
859
893
|
mic_pcm: MicPcmEvent
|
|
860
894
|
mic_lc3: MicLc3Event
|
|
861
895
|
stream_status: StreamStatusEvent
|
|
862
|
-
ota_update_available: OtaUpdateAvailableEvent
|
|
863
896
|
ota_start_ack: OtaStartAckEvent
|
|
864
897
|
ota_status: OtaStatusEvent
|
|
865
898
|
version_info: VersionInfoEvent
|
|
@@ -913,7 +946,7 @@ export interface BluetoothSdkPublicModule {
|
|
|
913
946
|
|
|
914
947
|
setGalleryModeEnabled(enabled: boolean): Promise<SettingsAckSuccessEvent>
|
|
915
948
|
setVoiceActivityDetectionEnabled(enabled: boolean): Promise<void>
|
|
916
|
-
setButtonPhotoSettings(
|
|
949
|
+
setButtonPhotoSettings(settings: ButtonPhotoSettings): Promise<SettingsAckSuccessEvent>
|
|
917
950
|
setButtonVideoRecordingSettings(width: number, height: number, fps: number): Promise<SettingsAckSuccessEvent>
|
|
918
951
|
setButtonCameraLed(enabled: boolean): Promise<SettingsAckSuccessEvent>
|
|
919
952
|
setButtonMaxRecordingTime(minutes: number): Promise<SettingsAckSuccessEvent>
|
|
@@ -959,12 +992,10 @@ export interface BluetoothSdkPublicModule {
|
|
|
959
992
|
): Promise<RgbLedControlSuccessResponseEvent>
|
|
960
993
|
|
|
961
994
|
requestVersionInfo(): Promise<VersionInfoResult>
|
|
962
|
-
/**
|
|
963
|
-
checkForOtaUpdate(): Promise<
|
|
964
|
-
/** Start the OTA flow
|
|
995
|
+
/** Fetch the configured OTA manifest and return whether any ASG/BES/MTK update is available. */
|
|
996
|
+
checkForOtaUpdate(): Promise<boolean>
|
|
997
|
+
/** Start the OTA flow with the same configured manifest URL used by checkForOtaUpdate(). */
|
|
965
998
|
startOtaUpdate(): Promise<OtaStartAckEvent>
|
|
966
|
-
/** Re-run the glasses-side OTA version check, mainly after correcting clock skew/TLS failures. */
|
|
967
|
-
retryOtaVersionCheck(): Promise<OtaQueryResult>
|
|
968
999
|
|
|
969
1000
|
// // stt commands (MOVE TO CRUST)
|
|
970
1001
|
// setSttModelDetails(path: string, languageCode: string): Promise<void>
|
|
@@ -2,10 +2,9 @@ import {NativeModule, requireNativeModule} from "expo"
|
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
BluetoothSettingsUpdate,
|
|
5
|
-
BluetoothSdkPublicModule,
|
|
6
5
|
BluetoothSdkModuleEvents,
|
|
7
6
|
BluetoothStatus,
|
|
8
|
-
|
|
7
|
+
ButtonPhotoSettings,
|
|
9
8
|
CalendarEvent,
|
|
10
9
|
CAMERA_FOV_DEFAULT,
|
|
11
10
|
CAMERA_FOV_MAX,
|
|
@@ -122,7 +121,8 @@ declare class BluetoothSdkNativeModule extends NativeModule<BluetoothSdkModuleEv
|
|
|
122
121
|
// Gallery Commands
|
|
123
122
|
setGalleryModeEnabled(enabled: boolean): Promise<SettingsAckSuccessEvent>
|
|
124
123
|
setVoiceActivityDetectionEnabled(enabled: boolean): Promise<void>
|
|
125
|
-
setButtonPhotoSettings(
|
|
124
|
+
setButtonPhotoSettings(settings: ButtonPhotoSettings): Promise<SettingsAckSuccessEvent>
|
|
125
|
+
setButtonPhotoCaptureSettings(settings: ButtonPhotoSettings): Promise<SettingsAckSuccessEvent>
|
|
126
126
|
setButtonVideoRecordingSettings(width: number, height: number, fps: number): Promise<SettingsAckSuccessEvent>
|
|
127
127
|
setButtonCameraLed(enabled: boolean): Promise<SettingsAckSuccessEvent>
|
|
128
128
|
setButtonMaxRecordingTime(minutes: number): Promise<SettingsAckSuccessEvent>
|
|
@@ -131,12 +131,13 @@ declare class BluetoothSdkNativeModule extends NativeModule<BluetoothSdkModuleEv
|
|
|
131
131
|
requestPhoto(params: PhotoRequestParams): Promise<PhotoSuccessResponseEvent>
|
|
132
132
|
|
|
133
133
|
// OTA Commands
|
|
134
|
-
|
|
134
|
+
setOtaVersionUrl(otaVersionUrl: string): void
|
|
135
|
+
getOtaVersionUrl(): string
|
|
136
|
+
checkForOtaUpdate(): Promise<boolean>
|
|
137
|
+
startOtaUpdate(otaVersionUrl?: string | null): Promise<OtaStartAckEvent>
|
|
135
138
|
sendOtaQueryStatus(): Promise<OtaQueryResult>
|
|
136
139
|
/** Re-run glasses-side OTA version check (called after a clock fix invalidates a TLS failure). */
|
|
137
140
|
retryOtaVersionCheck(): Promise<OtaQueryResult>
|
|
138
|
-
checkForOtaUpdate(): Promise<OtaQueryResult>
|
|
139
|
-
startOtaUpdate(): Promise<OtaStartAckEvent>
|
|
140
141
|
|
|
141
142
|
// Version Info Commands
|
|
142
143
|
requestVersionInfo(): Promise<VersionInfoResult>
|
|
@@ -571,8 +572,6 @@ const nativeStartStream = NativeBluetoothSdkModule.startStream.bind(NativeBlueto
|
|
|
571
572
|
NativeBluetoothSdkModule.startExternallyManagedStream = function (params: StreamStartRequest) {
|
|
572
573
|
return nativeStartStream({...params, keepAliveMode: "external"} as StreamStartRequest)
|
|
573
574
|
}
|
|
574
|
-
NativeBluetoothSdkModule.checkForOtaUpdate = NativeBluetoothSdkModule.sendOtaQueryStatus.bind(NativeBluetoothSdkModule)
|
|
575
|
-
NativeBluetoothSdkModule.startOtaUpdate = NativeBluetoothSdkModule.sendOtaStart.bind(NativeBluetoothSdkModule)
|
|
576
575
|
|
|
577
576
|
export default NativeBluetoothSdkModule
|
|
578
|
-
export const BluetoothSdk = NativeBluetoothSdkModule as
|
|
577
|
+
export const BluetoothSdk = NativeBluetoothSdkModule as BluetoothSdkInternalModule
|
|
@@ -1,4 +1,23 @@
|
|
|
1
|
-
import type {PhotoRequestParams} from "../BluetoothSdk.types"
|
|
1
|
+
import type {PhotoRequestParams, PhotoSize} from "../BluetoothSdk.types"
|
|
2
|
+
|
|
3
|
+
/** Maps unknown/legacy size strings to the current wire format. */
|
|
4
|
+
export function normalizePhotoSizeTier(size: string | undefined): PhotoSize {
|
|
5
|
+
switch (size) {
|
|
6
|
+
case "small":
|
|
7
|
+
return "low"
|
|
8
|
+
case "large":
|
|
9
|
+
return "high"
|
|
10
|
+
case "full":
|
|
11
|
+
return "max"
|
|
12
|
+
case "low":
|
|
13
|
+
case "medium":
|
|
14
|
+
case "high":
|
|
15
|
+
case "max":
|
|
16
|
+
return size
|
|
17
|
+
default:
|
|
18
|
+
return "medium"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
2
21
|
|
|
3
22
|
/** Expo Android bridge rejects null values in Map<String, Any> — omit optional nullish fields. */
|
|
4
23
|
export function photoRequestParamsForNative(
|
|
@@ -7,7 +26,7 @@ export function photoRequestParamsForNative(
|
|
|
7
26
|
const payload: Record<string, string | number | boolean> = {
|
|
8
27
|
requestId: params.requestId,
|
|
9
28
|
appId: params.appId,
|
|
10
|
-
size: params.size,
|
|
29
|
+
size: normalizePhotoSizeTier(params.size),
|
|
11
30
|
webhookUrl: params.webhookUrl ?? "",
|
|
12
31
|
compress: params.compress,
|
|
13
32
|
flash: true,
|
|
@@ -24,5 +43,29 @@ export function photoRequestParamsForNative(
|
|
|
24
43
|
if (hasManualExposure && params.iso != null && Number.isFinite(params.iso) && params.iso > 0) {
|
|
25
44
|
payload.iso = Math.round(params.iso)
|
|
26
45
|
}
|
|
46
|
+
if (params.aeExposureDivisor != null && params.aeExposureDivisor > 1) {
|
|
47
|
+
payload.aeExposureDivisor = Math.round(params.aeExposureDivisor)
|
|
48
|
+
}
|
|
49
|
+
if (params.isoCap != null && params.isoCap > 0) {
|
|
50
|
+
payload.isoCap = Math.round(params.isoCap)
|
|
51
|
+
}
|
|
52
|
+
if (params.noiseReduction != null) {
|
|
53
|
+
payload.noiseReduction = params.noiseReduction
|
|
54
|
+
}
|
|
55
|
+
if (params.edgeEnhancement != null) {
|
|
56
|
+
payload.edgeEnhancement = params.edgeEnhancement
|
|
57
|
+
}
|
|
58
|
+
if (params.mfnr != null) {
|
|
59
|
+
payload.mfnr = params.mfnr
|
|
60
|
+
}
|
|
61
|
+
if (params.zsl != null) {
|
|
62
|
+
payload.zsl = params.zsl
|
|
63
|
+
}
|
|
64
|
+
if (params.ispDigitalGain != null) {
|
|
65
|
+
payload.ispDigitalGain = Math.round(params.ispDigitalGain)
|
|
66
|
+
}
|
|
67
|
+
if (params.ispAnalogGain != null && params.ispAnalogGain.length > 0) {
|
|
68
|
+
payload.ispAnalogGain = params.ispAnalogGain
|
|
69
|
+
}
|
|
27
70
|
return payload
|
|
28
71
|
}
|
package/src/debug.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import PrivateBluetoothSdkModule from "./_private/BluetoothSdkModule"
|
|
2
|
+
|
|
3
|
+
export function setOtaVersionUrl(otaVersionUrl: string): void {
|
|
4
|
+
PrivateBluetoothSdkModule.setOtaVersionUrl(otaVersionUrl)
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function getOtaVersionUrl(): string {
|
|
8
|
+
return PrivateBluetoothSdkModule.getOtaVersionUrl()
|
|
9
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import PrivateBluetoothSdkModule from "./_private/BluetoothSdkModule"
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
BluetoothSdkEventListener,
|
|
4
|
+
BluetoothSdkEventName,
|
|
5
|
+
BluetoothSdkPublicModule,
|
|
6
|
+
ButtonPhotoSettings,
|
|
7
|
+
} from "./BluetoothSdk.types"
|
|
3
8
|
|
|
4
9
|
const PUBLIC_EVENT_NAMES = new Set<BluetoothSdkEventName>([
|
|
5
10
|
"log",
|
|
@@ -36,7 +41,6 @@ const PUBLIC_EVENT_NAMES = new Set<BluetoothSdkEventName>([
|
|
|
36
41
|
"mic_pcm",
|
|
37
42
|
"mic_lc3",
|
|
38
43
|
"stream_status",
|
|
39
|
-
"ota_update_available",
|
|
40
44
|
"ota_start_ack",
|
|
41
45
|
"ota_status",
|
|
42
46
|
"version_info",
|
|
@@ -50,6 +54,10 @@ const addListener: BluetoothSdkPublicModule["addListener"] = (eventName, listene
|
|
|
50
54
|
return PrivateBluetoothSdkModule.addListener(eventName, listener as BluetoothSdkEventListener<BluetoothSdkEventName>)
|
|
51
55
|
}
|
|
52
56
|
|
|
57
|
+
const startOtaUpdate: BluetoothSdkPublicModule["startOtaUpdate"] = () => {
|
|
58
|
+
return PrivateBluetoothSdkModule.startOtaUpdate()
|
|
59
|
+
}
|
|
60
|
+
|
|
53
61
|
export const BluetoothSdk: BluetoothSdkPublicModule = Object.freeze({
|
|
54
62
|
addListener,
|
|
55
63
|
getDefaultDevice: PrivateBluetoothSdkModule.getDefaultDevice.bind(PrivateBluetoothSdkModule),
|
|
@@ -77,7 +85,15 @@ export const BluetoothSdk: BluetoothSdkPublicModule = Object.freeze({
|
|
|
77
85
|
setGalleryModeEnabled: PrivateBluetoothSdkModule.setGalleryModeEnabled.bind(PrivateBluetoothSdkModule),
|
|
78
86
|
setVoiceActivityDetectionEnabled:
|
|
79
87
|
PrivateBluetoothSdkModule.setVoiceActivityDetectionEnabled.bind(PrivateBluetoothSdkModule),
|
|
80
|
-
setButtonPhotoSettings:
|
|
88
|
+
setButtonPhotoSettings: (settings: ButtonPhotoSettings) => {
|
|
89
|
+
// setButtonPhotoCaptureSettings is available in SDK 0.1.13+. Guard for OTA version-skew
|
|
90
|
+
// where a new JS bundle runs against an older native module that only has the string form.
|
|
91
|
+
if (typeof PrivateBluetoothSdkModule.setButtonPhotoCaptureSettings === "function") {
|
|
92
|
+
return PrivateBluetoothSdkModule.setButtonPhotoCaptureSettings(settings)
|
|
93
|
+
}
|
|
94
|
+
// Legacy fallback: old native bridge only accepts a size string
|
|
95
|
+
return PrivateBluetoothSdkModule.setButtonPhotoSettings({size: settings.size ?? "max"} as any)
|
|
96
|
+
},
|
|
81
97
|
setButtonVideoRecordingSettings:
|
|
82
98
|
PrivateBluetoothSdkModule.setButtonVideoRecordingSettings.bind(PrivateBluetoothSdkModule),
|
|
83
99
|
setButtonCameraLed: PrivateBluetoothSdkModule.setButtonCameraLed.bind(PrivateBluetoothSdkModule),
|
|
@@ -96,9 +112,8 @@ export const BluetoothSdk: BluetoothSdkPublicModule = Object.freeze({
|
|
|
96
112
|
setGlassesMediaVolume: PrivateBluetoothSdkModule.setGlassesMediaVolume.bind(PrivateBluetoothSdkModule),
|
|
97
113
|
rgbLedControl: PrivateBluetoothSdkModule.rgbLedControl.bind(PrivateBluetoothSdkModule),
|
|
98
114
|
requestVersionInfo: PrivateBluetoothSdkModule.requestVersionInfo.bind(PrivateBluetoothSdkModule),
|
|
99
|
-
checkForOtaUpdate: PrivateBluetoothSdkModule.
|
|
100
|
-
startOtaUpdate
|
|
101
|
-
retryOtaVersionCheck: PrivateBluetoothSdkModule.retryOtaVersionCheck.bind(PrivateBluetoothSdkModule),
|
|
115
|
+
checkForOtaUpdate: PrivateBluetoothSdkModule.checkForOtaUpdate.bind(PrivateBluetoothSdkModule),
|
|
116
|
+
startOtaUpdate,
|
|
102
117
|
setSttModelDetails: PrivateBluetoothSdkModule.setSttModelDetails.bind(PrivateBluetoothSdkModule),
|
|
103
118
|
getSttModelPath: PrivateBluetoothSdkModule.getSttModelPath.bind(PrivateBluetoothSdkModule),
|
|
104
119
|
checkSttModelAvailable: PrivateBluetoothSdkModule.checkSttModelAvailable.bind(PrivateBluetoothSdkModule),
|
|
@@ -140,6 +155,7 @@ export type {
|
|
|
140
155
|
BluetoothSdkPublicModule as BluetoothSdkModule,
|
|
141
156
|
BluetoothSdkSubscription,
|
|
142
157
|
ButtonPhotoSize,
|
|
158
|
+
ButtonPhotoSettings,
|
|
143
159
|
ButtonPressEvent,
|
|
144
160
|
CameraFovPreset,
|
|
145
161
|
CameraFovRequest,
|
|
@@ -181,6 +197,7 @@ export type {
|
|
|
181
197
|
PhotoMeteredPreview,
|
|
182
198
|
PhotoResponseEvent,
|
|
183
199
|
PhotoRequestedCaptureConfig,
|
|
200
|
+
PhotoRequestParams,
|
|
184
201
|
PhotoSize,
|
|
185
202
|
PhotoStatusEvent,
|
|
186
203
|
PhotoStatusState,
|