@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
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
package com.mentra.bluetoothsdk
|
|
2
2
|
|
|
3
|
-
enum class GalleryMode {
|
|
4
|
-
AUTO,
|
|
5
|
-
MANUAL,
|
|
6
|
-
}
|
|
7
|
-
|
|
8
3
|
enum class PhotoSize(val value: String) {
|
|
9
4
|
SMALL("small"),
|
|
10
5
|
MEDIUM("medium"),
|
|
@@ -49,7 +44,7 @@ data class ButtonPhotoSettings(
|
|
|
49
44
|
data class ButtonVideoRecordingSettings(
|
|
50
45
|
val width: Int,
|
|
51
46
|
val height: Int,
|
|
52
|
-
val
|
|
47
|
+
val fps: Int,
|
|
53
48
|
)
|
|
54
49
|
|
|
55
50
|
enum class CameraFov(val fov: Int, val roiPosition: Int) {
|
|
@@ -64,8 +59,35 @@ data class PhotoRequest @JvmOverloads constructor(
|
|
|
64
59
|
val webhookUrl: String,
|
|
65
60
|
val authToken: String? = null,
|
|
66
61
|
val compress: PhotoCompression = PhotoCompression.MEDIUM,
|
|
62
|
+
val flash: Boolean = true,
|
|
67
63
|
val sound: Boolean = true,
|
|
68
|
-
)
|
|
64
|
+
/** Sensor exposure time for this capture only (ns), or null for auto exposure */
|
|
65
|
+
val exposureTimeNs: Double? = null,
|
|
66
|
+
) {
|
|
67
|
+
companion object {
|
|
68
|
+
/** Mirrors iOS `BluetoothSdkModule` defaults for keys omitted from the JS bridge. */
|
|
69
|
+
@JvmStatic
|
|
70
|
+
fun fromMap(values: Map<String, Any>): PhotoRequest {
|
|
71
|
+
val rawExp = values["exposureTimeNs"] ?: values["exposure_time_ns"]
|
|
72
|
+
val exposureTimeNs: Double? =
|
|
73
|
+
when (rawExp) {
|
|
74
|
+
is Number -> rawExp.toDouble().takeIf { it.isFinite() && it > 0 }
|
|
75
|
+
else -> null
|
|
76
|
+
}
|
|
77
|
+
return PhotoRequest(
|
|
78
|
+
requestId = stringValue(values, "requestId", "request_id").orEmpty(),
|
|
79
|
+
appId = stringValue(values, "appId", "app_id").orEmpty(),
|
|
80
|
+
size = PhotoSize.fromValue(stringValue(values, "size") ?: "medium"),
|
|
81
|
+
webhookUrl = stringValue(values, "webhookUrl", "webhook_url").orEmpty(),
|
|
82
|
+
authToken = stringValue(values, "authToken", "auth_token")?.takeIf { it.isNotBlank() },
|
|
83
|
+
compress = PhotoCompression.fromValue(stringValue(values, "compress") ?: "none"),
|
|
84
|
+
flash = boolValue(values, "flash") ?: true,
|
|
85
|
+
sound = boolValue(values, "sound") ?: true,
|
|
86
|
+
exposureTimeNs = exposureTimeNs,
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
69
91
|
|
|
70
92
|
enum class RgbLedAction(val value: String) {
|
|
71
93
|
ON("on"),
|
|
@@ -22,7 +22,8 @@ abstract class ControllerManager {
|
|
|
22
22
|
authToken: String?,
|
|
23
23
|
compress: String?,
|
|
24
24
|
flash: Boolean,
|
|
25
|
-
sound: Boolean
|
|
25
|
+
sound: Boolean,
|
|
26
|
+
exposureTimeNs: Long?,
|
|
26
27
|
)
|
|
27
28
|
abstract fun startStream(message: Map<String, Any>)
|
|
28
29
|
abstract fun stopStream()
|
|
@@ -132,8 +133,8 @@ abstract class ControllerManager {
|
|
|
132
133
|
val micEnabled: Boolean
|
|
133
134
|
get() = DeviceStore.get("glasses", "micEnabled") as? Boolean ?: false
|
|
134
135
|
|
|
135
|
-
val
|
|
136
|
-
get() = DeviceStore.get("glasses", "
|
|
136
|
+
val voiceActivityDetectionEnabled: Boolean
|
|
137
|
+
get() = DeviceStore.get("glasses", "voiceActivityDetectionEnabled") as? Boolean ?: true
|
|
137
138
|
|
|
138
139
|
val batteryLevel: Int
|
|
139
140
|
get() = DeviceStore.get("glasses", "batteryLevel") as? Int ?: -1
|
|
@@ -863,7 +863,8 @@ class R1 : ControllerManager() {
|
|
|
863
863
|
override fun sendJson(jsonOriginal: Map<String, Any>, wakeUp: Boolean, requireAck: Boolean) {}
|
|
864
864
|
override fun requestPhoto(
|
|
865
865
|
requestId: String, appId: String, size: String?, webhookUrl: String?,
|
|
866
|
-
authToken: String?, compress: String?, flash: Boolean, sound: Boolean
|
|
866
|
+
authToken: String?, compress: String?, flash: Boolean, sound: Boolean,
|
|
867
|
+
exposureTimeNs: Long?,
|
|
867
868
|
) {}
|
|
868
869
|
override fun startVideoRecording(requestId: String, save: Boolean, flash: Boolean, sound: Boolean) {}
|
|
869
870
|
override fun stopVideoRecording(requestId: String) {}
|
|
@@ -29,6 +29,16 @@ data class BatteryStatusEvent(
|
|
|
29
29
|
val values: Map<String, Any>,
|
|
30
30
|
)
|
|
31
31
|
|
|
32
|
+
data class VoiceActivityDetectionStatusEvent(
|
|
33
|
+
val voiceActivityDetectionEnabled: Boolean,
|
|
34
|
+
val values: Map<String, Any>,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
data class SpeakingStatusEvent(
|
|
38
|
+
val speaking: Boolean,
|
|
39
|
+
val values: Map<String, Any>,
|
|
40
|
+
)
|
|
41
|
+
|
|
32
42
|
interface MentraBluetoothSdkListener {
|
|
33
43
|
fun onStateChanged(state: MentraBluetoothState) {}
|
|
34
44
|
fun onGlassesChanged(glasses: GlassesRuntimeState) {}
|
|
@@ -40,6 +50,8 @@ interface MentraBluetoothSdkListener {
|
|
|
40
50
|
fun onTouch(event: TouchEvent) {}
|
|
41
51
|
fun onSwipe(event: SwipeEvent) {}
|
|
42
52
|
fun onHeadUpChanged(headUp: Boolean) {}
|
|
53
|
+
fun onVoiceActivityDetectionStatus(event: VoiceActivityDetectionStatusEvent) {}
|
|
54
|
+
fun onSpeakingStatus(event: SpeakingStatusEvent) {}
|
|
43
55
|
fun onBatteryStatus(event: BatteryStatusEvent) {}
|
|
44
56
|
fun onWifiStatusChanged(event: WifiStatusEvent) {}
|
|
45
57
|
fun onHotspotStatusChanged(event: HotspotStatusEvent) {}
|
|
@@ -1530,7 +1530,7 @@ public class G1 extends SGCManager {
|
|
|
1530
1530
|
}
|
|
1531
1531
|
|
|
1532
1532
|
@Override
|
|
1533
|
-
public void requestPhoto(String requestId, String appId, String size, String webhookUrl, String authToken, String compress, boolean flash, boolean sound) {
|
|
1533
|
+
public void requestPhoto(String requestId, String appId, String size, String webhookUrl, String authToken, String compress, boolean flash, boolean sound, Long exposureTimeNs) {
|
|
1534
1534
|
|
|
1535
1535
|
}
|
|
1536
1536
|
|
|
@@ -2360,7 +2360,8 @@ class G2 : SGCManager() {
|
|
|
2360
2360
|
authToken: String?,
|
|
2361
2361
|
compress: String?,
|
|
2362
2362
|
flash: Boolean,
|
|
2363
|
-
sound: Boolean
|
|
2363
|
+
sound: Boolean,
|
|
2364
|
+
exposureTimeNs: Long?,
|
|
2364
2365
|
) {
|
|
2365
2366
|
Bridge.log("G2: requestPhoto - not supported (no camera)")
|
|
2366
2367
|
}
|
|
@@ -132,7 +132,7 @@ public class Mach1 extends SGCManager {
|
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
@Override
|
|
135
|
-
public void requestPhoto(@NonNull String requestId, @NonNull String appId, @NonNull String size, @Nullable String webhookUrl, @Nullable String authToken, @Nullable String compress, boolean flash, boolean sound) {
|
|
135
|
+
public void requestPhoto(@NonNull String requestId, @NonNull String appId, @NonNull String size, @Nullable String webhookUrl, @Nullable String authToken, @Nullable String compress, boolean flash, boolean sound, @Nullable Long exposureTimeNs) {
|
|
136
136
|
|
|
137
137
|
}
|
|
138
138
|
|
|
@@ -105,6 +105,7 @@ public class MentraLive extends SGCManager {
|
|
|
105
105
|
|
|
106
106
|
// LC3 frame size for Mentra Live
|
|
107
107
|
private static final int LC3_FRAME_SIZE = 40;
|
|
108
|
+
private static final int VOICE_ACTIVITY_DETECTION_SWITCH_TYPE = 8;
|
|
108
109
|
|
|
109
110
|
// Local-only fields (not in parent SGCManager)
|
|
110
111
|
private int buildNumberInt = 0; // Build number as integer for version checks
|
|
@@ -1777,6 +1778,9 @@ public class MentraLive extends SGCManager {
|
|
|
1777
1778
|
if (buildNumberInt < 5) {
|
|
1778
1779
|
String jsonStr = json.toString();
|
|
1779
1780
|
// Bridge.log("LIVE: 📤 Sending JSON with esoteric message ID: " + jsonStr);
|
|
1781
|
+
if ("take_photo".equals(json.optString("type", ""))) {
|
|
1782
|
+
Bridge.log("LIVE: PHOTO PIPELINE [4/4] sendJson(build<5) -> sendDataToGlasses — " + summarizeOutgoingMessage(jsonStr));
|
|
1783
|
+
}
|
|
1780
1784
|
sendDataToGlasses(jsonStr, wakeup);
|
|
1781
1785
|
} else {
|
|
1782
1786
|
// Add esoteric message ID to the JSON
|
|
@@ -1812,6 +1816,9 @@ public class MentraLive extends SGCManager {
|
|
|
1812
1816
|
trackMessageForAck(messageId, jsonStr, ackTimeout);
|
|
1813
1817
|
|
|
1814
1818
|
// Send the data
|
|
1819
|
+
if ("take_photo".equals(json.optString("type", ""))) {
|
|
1820
|
+
Bridge.log("LIVE: PHOTO PIPELINE [4/4] sendJson -> sendDataToGlasses (mId=" + messageId + ", ackTimeoutMs=" + ackTimeout + ") — " + summarizeOutgoingMessage(jsonStr));
|
|
1821
|
+
}
|
|
1815
1822
|
sendDataToGlasses(jsonStr, wakeup);
|
|
1816
1823
|
}
|
|
1817
1824
|
} catch (JSONException e) {
|
|
@@ -2326,6 +2333,15 @@ public class MentraLive extends SGCManager {
|
|
|
2326
2333
|
}
|
|
2327
2334
|
break;
|
|
2328
2335
|
|
|
2336
|
+
case "voice_activity_detection_status":
|
|
2337
|
+
handleVoiceActivityDetectionStatus(
|
|
2338
|
+
json.optBoolean("voiceActivityDetectionEnabled", true));
|
|
2339
|
+
break;
|
|
2340
|
+
|
|
2341
|
+
case "speaking_status":
|
|
2342
|
+
handleSpeakingStatus(json.optBoolean("speaking", false));
|
|
2343
|
+
break;
|
|
2344
|
+
|
|
2329
2345
|
case "battery_status":
|
|
2330
2346
|
// Process battery status
|
|
2331
2347
|
int percent = json.optInt("percent", getBatteryLevel());
|
|
@@ -2617,15 +2633,14 @@ public class MentraLive extends SGCManager {
|
|
|
2617
2633
|
|
|
2618
2634
|
case "switch_status":
|
|
2619
2635
|
// Process switch status report from glasses
|
|
2620
|
-
int switchType = json.optInt("switch_type", -1);
|
|
2621
|
-
int switchValue = json.optInt("switch_value", -1);
|
|
2636
|
+
int switchType = json.has("switch_type") ? json.optInt("switch_type", -1) : json.optInt("switchType", -1);
|
|
2637
|
+
int switchValue = json.has("switch_value") ? json.optInt("switch_value", -1) : json.optInt("switchValue", -1);
|
|
2622
2638
|
long switchTimestamp = json.optLong("timestamp", System.currentTimeMillis());
|
|
2623
2639
|
|
|
2624
2640
|
Log.d(TAG, "🔘 Received switch status - Type: " + switchType +
|
|
2625
2641
|
", Value: " + switchValue);
|
|
2626
2642
|
|
|
2627
|
-
|
|
2628
|
-
Bridge.sendSwitchStatus(switchType, switchValue, switchTimestamp);
|
|
2643
|
+
handleSwitchStatus(switchType, switchValue, switchTimestamp);
|
|
2629
2644
|
break;
|
|
2630
2645
|
|
|
2631
2646
|
case "sensor_data":
|
|
@@ -3203,6 +3218,33 @@ public class MentraLive extends SGCManager {
|
|
|
3203
3218
|
handleSrVol(json);
|
|
3204
3219
|
break;
|
|
3205
3220
|
|
|
3221
|
+
case "sr_vad":
|
|
3222
|
+
try {
|
|
3223
|
+
JSONObject bodyObj = optK900Body(json);
|
|
3224
|
+
if (bodyObj != null) {
|
|
3225
|
+
int on = bodyObj.optInt("on", -1);
|
|
3226
|
+
if (on == 0 || on == 1) {
|
|
3227
|
+
handleSpeakingStatus(on == 1);
|
|
3228
|
+
}
|
|
3229
|
+
}
|
|
3230
|
+
} catch (Exception e) {
|
|
3231
|
+
Log.e(TAG, "Error parsing sr_vad response", e);
|
|
3232
|
+
}
|
|
3233
|
+
break;
|
|
3234
|
+
|
|
3235
|
+
case "sr_swit":
|
|
3236
|
+
try {
|
|
3237
|
+
JSONObject bodyObj = optK900Body(json);
|
|
3238
|
+
if (bodyObj != null) {
|
|
3239
|
+
int type = bodyObj.optInt("type", -1);
|
|
3240
|
+
int value = bodyObj.optInt("switch", -1);
|
|
3241
|
+
handleSwitchStatus(type, value, System.currentTimeMillis());
|
|
3242
|
+
}
|
|
3243
|
+
} catch (Exception e) {
|
|
3244
|
+
Log.e(TAG, "Error parsing sr_swit response", e);
|
|
3245
|
+
}
|
|
3246
|
+
break;
|
|
3247
|
+
|
|
3206
3248
|
case "sr_shut":
|
|
3207
3249
|
Bridge.log("LIVE: K900 shutdown command received - glasses shutting down");
|
|
3208
3250
|
lastShutdownTimeMs = System.currentTimeMillis();
|
|
@@ -3440,6 +3482,32 @@ public class MentraLive extends SGCManager {
|
|
|
3440
3482
|
}
|
|
3441
3483
|
}
|
|
3442
3484
|
|
|
3485
|
+
private void handleVoiceActivityDetectionStatus(boolean enabled) {
|
|
3486
|
+
Bridge.log("LIVE: Voice Activity Detection " + (enabled ? "enabled" : "disabled"));
|
|
3487
|
+
Bridge.sendVoiceActivityDetectionStatus(enabled);
|
|
3488
|
+
}
|
|
3489
|
+
|
|
3490
|
+
private void handleSpeakingStatus(boolean speaking) {
|
|
3491
|
+
if (!isVoiceActivityDetectionEnabled()) {
|
|
3492
|
+
Bridge.log("LIVE: Ignoring speaking status because Voice Activity Detection is disabled");
|
|
3493
|
+
return;
|
|
3494
|
+
}
|
|
3495
|
+
Bridge.log("LIVE: Speaking status " + (speaking ? "speaking" : "not speaking"));
|
|
3496
|
+
Bridge.sendSpeakingStatus(speaking);
|
|
3497
|
+
}
|
|
3498
|
+
|
|
3499
|
+
private boolean isVoiceActivityDetectionEnabled() {
|
|
3500
|
+
Object value = DeviceStore.INSTANCE.get("bluetooth", "voice_activity_detection_enabled");
|
|
3501
|
+
return !(value instanceof Boolean) || (Boolean) value;
|
|
3502
|
+
}
|
|
3503
|
+
|
|
3504
|
+
private void handleSwitchStatus(int switchType, int switchValue, long timestamp) {
|
|
3505
|
+
Bridge.sendSwitchStatus(switchType, switchValue, timestamp);
|
|
3506
|
+
if (switchType == VOICE_ACTIVITY_DETECTION_SWITCH_TYPE && (switchValue == 0 || switchValue == 1)) {
|
|
3507
|
+
handleVoiceActivityDetectionStatus(switchValue == 1);
|
|
3508
|
+
}
|
|
3509
|
+
}
|
|
3510
|
+
|
|
3443
3511
|
/**
|
|
3444
3512
|
* Update WiFi status and notify listeners
|
|
3445
3513
|
* Matches iOS MentraLive.swift updateWifiStatus pattern
|
|
@@ -3625,7 +3693,7 @@ public class MentraLive extends SGCManager {
|
|
|
3625
3693
|
|
|
3626
3694
|
@Override
|
|
3627
3695
|
public void sendGalleryMode() {
|
|
3628
|
-
boolean active = (Boolean) DeviceStore.INSTANCE.get("bluetooth", "
|
|
3696
|
+
boolean active = (Boolean) DeviceStore.INSTANCE.get("bluetooth", "gallery_mode");
|
|
3629
3697
|
Bridge.log("LIVE: 📸 Sending gallery mode active to glasses: " + active);
|
|
3630
3698
|
try {
|
|
3631
3699
|
JSONObject json = new JSONObject();
|
|
@@ -4092,9 +4160,10 @@ public class MentraLive extends SGCManager {
|
|
|
4092
4160
|
}
|
|
4093
4161
|
}
|
|
4094
4162
|
|
|
4095
|
-
public void requestPhoto(String requestId, String appId, String size, String webhookUrl, String authToken, String compress, boolean flash, boolean sound) {
|
|
4163
|
+
public void requestPhoto(String requestId, String appId, String size, String webhookUrl, String authToken, String compress, boolean flash, boolean sound, Long exposureTimeNs) {
|
|
4096
4164
|
boolean hasAuthToken = authToken != null && !authToken.isEmpty();
|
|
4097
|
-
Bridge.log("LIVE: Requesting photo: " + requestId + " for app: " + appId + " with size: " + size + ", webhookUrl: " + webhookUrl + ", authToken: " + (hasAuthToken ? "***" : "none") + ", compress=" + compress + ", flash=" + flash + ", sound=" + sound);
|
|
4165
|
+
Bridge.log("LIVE: Requesting photo: " + requestId + " for app: " + appId + " with size: " + size + ", webhookUrl: " + webhookUrl + ", authToken: " + (hasAuthToken ? "***" : "none") + ", compress=" + compress + ", flash=" + flash + ", sound=" + sound + ", exposureTimeNs=" + exposureTimeNs);
|
|
4166
|
+
Bridge.log("LIVE: PHOTO PIPELINE [5/6] requestPhoto() entry — requestId=" + requestId + ", appId=" + appId);
|
|
4098
4167
|
|
|
4099
4168
|
try {
|
|
4100
4169
|
JSONObject json = new JSONObject();
|
|
@@ -4117,6 +4186,10 @@ public class MentraLive extends SGCManager {
|
|
|
4117
4186
|
}
|
|
4118
4187
|
json.put("flash", flash);
|
|
4119
4188
|
json.put("sound", sound);
|
|
4189
|
+
if (exposureTimeNs != null && exposureTimeNs > 0L) {
|
|
4190
|
+
Bridge.log("LIVE: Using manual exposure time for photo request " + requestId + ": " + exposureTimeNs + " ns");
|
|
4191
|
+
json.put("exposureTimeNs", exposureTimeNs);
|
|
4192
|
+
}
|
|
4120
4193
|
|
|
4121
4194
|
// Always generate BLE ID for potential fallback
|
|
4122
4195
|
String bleImgId = "I" + String.format("%09d", System.currentTimeMillis() % 1000000000);
|
|
@@ -4134,6 +4207,8 @@ public class MentraLive extends SGCManager {
|
|
|
4134
4207
|
}
|
|
4135
4208
|
|
|
4136
4209
|
Bridge.log("LIVE: Using auto transfer mode with BLE fallback ID: " + bleImgId);
|
|
4210
|
+
Bridge.log("LIVE: PHOTO PIPELINE [5b/6] JSON ready — " + summarizeOutgoingMessage(json.toString()) + ", wakeup=true");
|
|
4211
|
+
Bridge.log("LIVE: PHOTO PIPELINE [6/6] Dispatching take_photo to sendJson()");
|
|
4137
4212
|
|
|
4138
4213
|
sendJson(json, true);
|
|
4139
4214
|
} catch (JSONException e) {
|
|
@@ -4759,7 +4834,7 @@ public class MentraLive extends SGCManager {
|
|
|
4759
4834
|
Map<String, Object> videoSettings = (Map<String, Object>) videoSettingsObj;
|
|
4760
4835
|
videoWidth = ((Number) videoSettings.getOrDefault("width", videoWidth)).intValue();
|
|
4761
4836
|
videoHeight = ((Number) videoSettings.getOrDefault("height", videoHeight)).intValue();
|
|
4762
|
-
videoFps = ((Number) videoSettings.getOrDefault("
|
|
4837
|
+
videoFps = ((Number) videoSettings.getOrDefault("fps", videoFps)).intValue();
|
|
4763
4838
|
} else {
|
|
4764
4839
|
Object width = DeviceStore.INSTANCE.get("bluetooth", "button_video_width");
|
|
4765
4840
|
Object height = DeviceStore.INSTANCE.get("bluetooth", "button_video_height");
|
|
@@ -5564,6 +5639,12 @@ public class MentraLive extends SGCManager {
|
|
|
5564
5639
|
}
|
|
5565
5640
|
|
|
5566
5641
|
try {
|
|
5642
|
+
String outgoingSummary = summarizeOutgoingMessage(data);
|
|
5643
|
+
boolean isPhotoRequest = outgoingSummary.contains("type=take_photo");
|
|
5644
|
+
if (isPhotoRequest) {
|
|
5645
|
+
Bridge.log("LIVE: PHOTO PIPELINE BLE handoff — sendDataToGlasses() start, wakeup=" + wakeup + ", " + outgoingSummary);
|
|
5646
|
+
}
|
|
5647
|
+
|
|
5567
5648
|
// First check if the message needs chunking
|
|
5568
5649
|
// Create a test C-wrapped version to check size
|
|
5569
5650
|
JSONObject testWrapper = new JSONObject();
|
|
@@ -5576,6 +5657,9 @@ public class MentraLive extends SGCManager {
|
|
|
5576
5657
|
// Check if chunking is needed
|
|
5577
5658
|
if (MessageChunker.needsChunking(testWrappedJson)) {
|
|
5578
5659
|
Bridge.log("LIVE: Message exceeds threshold, chunking required");
|
|
5660
|
+
if (isPhotoRequest) {
|
|
5661
|
+
Bridge.log("LIVE: PHOTO PIPELINE BLE handoff — chunking enabled for request payload");
|
|
5662
|
+
}
|
|
5579
5663
|
|
|
5580
5664
|
// Extract message ID if present for ACK tracking
|
|
5581
5665
|
long messageId = -1;
|
|
@@ -5589,6 +5673,9 @@ public class MentraLive extends SGCManager {
|
|
|
5589
5673
|
// Create chunks
|
|
5590
5674
|
List<JSONObject> chunks = MessageChunker.createChunks(data, messageId);
|
|
5591
5675
|
Bridge.log("LIVE: Sending " + chunks.size() + " chunks");
|
|
5676
|
+
if (isPhotoRequest) {
|
|
5677
|
+
Bridge.log("LIVE: PHOTO PIPELINE BLE handoff — created " + chunks.size() + " chunks for transmission");
|
|
5678
|
+
}
|
|
5592
5679
|
|
|
5593
5680
|
// Send each chunk
|
|
5594
5681
|
for (int i = 0; i < chunks.size(); i++) {
|
|
@@ -5612,6 +5699,9 @@ public class MentraLive extends SGCManager {
|
|
|
5612
5699
|
}
|
|
5613
5700
|
|
|
5614
5701
|
Bridge.log("LIVE: All chunks queued for transmission");
|
|
5702
|
+
if (isPhotoRequest) {
|
|
5703
|
+
Bridge.log("LIVE: PHOTO PIPELINE BLE handoff — all photo chunks queued");
|
|
5704
|
+
}
|
|
5615
5705
|
} else {
|
|
5616
5706
|
// Normal single message transmission
|
|
5617
5707
|
Bridge.log("LIVE: Sending data to glasses: " + data);
|
|
@@ -5621,6 +5711,9 @@ public class MentraLive extends SGCManager {
|
|
|
5621
5711
|
|
|
5622
5712
|
// Queue the data for sending
|
|
5623
5713
|
queueData(packedData);
|
|
5714
|
+
if (isPhotoRequest) {
|
|
5715
|
+
Bridge.log("LIVE: PHOTO PIPELINE BLE handoff — packedLen=" + packedData.length + " bytes queued");
|
|
5716
|
+
}
|
|
5624
5717
|
}
|
|
5625
5718
|
|
|
5626
5719
|
} catch (Exception e) {
|
|
@@ -5628,6 +5721,31 @@ public class MentraLive extends SGCManager {
|
|
|
5628
5721
|
}
|
|
5629
5722
|
}
|
|
5630
5723
|
|
|
5724
|
+
private String summarizeOutgoingMessage(String payload) {
|
|
5725
|
+
if (payload == null || payload.isEmpty()) {
|
|
5726
|
+
return "type=unknown, requestId=none, appId=none, transferMethod=none, bleImgId=none, exposureTimeNs=none, mId=none";
|
|
5727
|
+
}
|
|
5728
|
+
try {
|
|
5729
|
+
JSONObject obj = new JSONObject(payload);
|
|
5730
|
+
String type = obj.optString("type", "unknown");
|
|
5731
|
+
String requestId = obj.optString("requestId", "none");
|
|
5732
|
+
String appId = obj.optString("appId", "none");
|
|
5733
|
+
String transferMethod = obj.optString("transferMethod", "none");
|
|
5734
|
+
String bleImgId = obj.optString("bleImgId", "none");
|
|
5735
|
+
String exposure = obj.has("exposureTimeNs") ? String.valueOf(obj.optLong("exposureTimeNs")) : "none";
|
|
5736
|
+
String mId = obj.has("mId") ? String.valueOf(obj.optLong("mId")) : "none";
|
|
5737
|
+
return "type=" + type
|
|
5738
|
+
+ ", requestId=" + requestId
|
|
5739
|
+
+ ", appId=" + appId
|
|
5740
|
+
+ ", transferMethod=" + transferMethod
|
|
5741
|
+
+ ", bleImgId=" + bleImgId
|
|
5742
|
+
+ ", exposureTimeNs=" + exposure
|
|
5743
|
+
+ ", mId=" + mId;
|
|
5744
|
+
} catch (JSONException ignored) {
|
|
5745
|
+
return "type=non_json, payloadLen=" + payload.length();
|
|
5746
|
+
}
|
|
5747
|
+
}
|
|
5748
|
+
|
|
5631
5749
|
public void sendStartVideoStream(){
|
|
5632
5750
|
try {
|
|
5633
5751
|
JSONObject command = new JSONObject();
|
|
@@ -6437,6 +6555,46 @@ public class MentraLive extends SGCManager {
|
|
|
6437
6555
|
|
|
6438
6556
|
// Send gallery mode state (camera app running status)
|
|
6439
6557
|
sendGalleryMode();
|
|
6558
|
+
|
|
6559
|
+
// Send glasses-side Voice Activity Detection setting.
|
|
6560
|
+
sendVoiceActivityDetectionSetting();
|
|
6561
|
+
}
|
|
6562
|
+
|
|
6563
|
+
@Override
|
|
6564
|
+
public void sendVoiceActivityDetectionSetting() {
|
|
6565
|
+
Object value = DeviceStore.INSTANCE.get("bluetooth", "voice_activity_detection_enabled");
|
|
6566
|
+
boolean enabled = value instanceof Boolean ? (Boolean) value : true;
|
|
6567
|
+
|
|
6568
|
+
Bridge.log("LIVE: 🎤 Sending Voice Activity Detection setting to glasses: " + enabled);
|
|
6569
|
+
|
|
6570
|
+
if (!isConnected) {
|
|
6571
|
+
Bridge.log("LIVE: Cannot send Voice Activity Detection setting - not connected");
|
|
6572
|
+
return;
|
|
6573
|
+
}
|
|
6574
|
+
|
|
6575
|
+
try {
|
|
6576
|
+
JSONObject body = new JSONObject();
|
|
6577
|
+
body.put("type", VOICE_ACTIVITY_DETECTION_SWITCH_TYPE);
|
|
6578
|
+
body.put("switch", enabled ? 1 : 0);
|
|
6579
|
+
|
|
6580
|
+
JSONObject cmdObject = new JSONObject();
|
|
6581
|
+
cmdObject.put("C", "cs_swit");
|
|
6582
|
+
cmdObject.put("V", 1);
|
|
6583
|
+
cmdObject.put("B", body.toString());
|
|
6584
|
+
|
|
6585
|
+
byte[] packedData =
|
|
6586
|
+
K900ProtocolUtils.packDataToK900(
|
|
6587
|
+
cmdObject.toString().getBytes(StandardCharsets.UTF_8),
|
|
6588
|
+
K900ProtocolUtils.CMD_TYPE_STRING);
|
|
6589
|
+
if (packedData == null) {
|
|
6590
|
+
Bridge.log("LIVE: Failed to pack Voice Activity Detection setting command");
|
|
6591
|
+
return;
|
|
6592
|
+
}
|
|
6593
|
+
queueData(packedData);
|
|
6594
|
+
Bridge.sendVoiceActivityDetectionStatus(enabled);
|
|
6595
|
+
} catch (JSONException e) {
|
|
6596
|
+
Log.e(TAG, "Error creating Voice Activity Detection setting command", e);
|
|
6597
|
+
}
|
|
6440
6598
|
}
|
|
6441
6599
|
|
|
6442
6600
|
/**
|
|
@@ -6487,7 +6645,7 @@ public class MentraLive extends SGCManager {
|
|
|
6487
6645
|
}
|
|
6488
6646
|
|
|
6489
6647
|
/**
|
|
6490
|
-
* Send camera FOV setting to glasses (K900 / Mentra Live).
|
|
6648
|
+
* Send camera FOV setting to glasses (K900 / Mentra Live).
|
|
6491
6649
|
*/
|
|
6492
6650
|
@Override
|
|
6493
6651
|
public void sendCameraFovSetting() {
|
|
@@ -6499,7 +6657,7 @@ public class MentraLive extends SGCManager {
|
|
|
6499
6657
|
@SuppressWarnings("unchecked")
|
|
6500
6658
|
java.util.Map<String, Object> map = (java.util.Map<String, Object>) raw;
|
|
6501
6659
|
Object f = map.get("fov");
|
|
6502
|
-
Object r = map.get("
|
|
6660
|
+
Object r = map.get("roi_position");
|
|
6503
6661
|
if (f instanceof Number) fov = ((Number) f).intValue();
|
|
6504
6662
|
if (r instanceof Number) roiPosition = ((Number) r).intValue();
|
|
6505
6663
|
}
|
|
@@ -290,7 +290,7 @@ class MentraNex : SGCManager() {
|
|
|
290
290
|
override fun requestVersionInfo() { Bridge.log("Nex: requestVersionInfo operation not supported") }
|
|
291
291
|
|
|
292
292
|
// Camera & Media: Not supported on Nex (No camera)
|
|
293
|
-
override fun requestPhoto(requestId: String, appId: String, size: String, webhookUrl: String?, authToken: String?, compress: String?, flash: Boolean, sound: Boolean) { Bridge.log("Nex: requestPhoto operation not supported") }
|
|
293
|
+
override fun requestPhoto(requestId: String, appId: String, size: String, webhookUrl: String?, authToken: String?, compress: String?, flash: Boolean, sound: Boolean, exposureTimeNs: Long?) { Bridge.log("Nex: requestPhoto operation not supported") }
|
|
294
294
|
override fun startStream(message: MutableMap<String, Any>) { Bridge.log("Nex: startStream operation not supported") }
|
|
295
295
|
override fun stopStream() { Bridge.log("Nex: stopStream operation not supported") }
|
|
296
296
|
override fun sendStreamKeepAlive(message: MutableMap<String, Any>) { Bridge.log("Nex: sendStreamKeepAlive operation not supported") }
|
|
@@ -21,7 +21,8 @@ abstract class SGCManager {
|
|
|
21
21
|
authToken: String?,
|
|
22
22
|
compress: String?,
|
|
23
23
|
flash: Boolean,
|
|
24
|
-
sound: Boolean
|
|
24
|
+
sound: Boolean,
|
|
25
|
+
exposureTimeNs: Long?,
|
|
25
26
|
)
|
|
26
27
|
abstract fun startStream(message: MutableMap<String, Any>)
|
|
27
28
|
abstract fun stopStream()
|
|
@@ -109,6 +110,9 @@ abstract class SGCManager {
|
|
|
109
110
|
abstract fun queryGalleryStatus()
|
|
110
111
|
abstract fun sendGalleryMode()
|
|
111
112
|
|
|
113
|
+
// Voice Activity Detection
|
|
114
|
+
open fun sendVoiceActivityDetectionSetting() {}
|
|
115
|
+
|
|
112
116
|
// Version info
|
|
113
117
|
abstract fun requestVersionInfo()
|
|
114
118
|
|
|
@@ -155,8 +159,8 @@ abstract class SGCManager {
|
|
|
155
159
|
val micEnabled: Boolean
|
|
156
160
|
get() = DeviceStore.get("glasses", "micEnabled") as? Boolean ?: false
|
|
157
161
|
|
|
158
|
-
val
|
|
159
|
-
get() = DeviceStore.get("glasses", "
|
|
162
|
+
val voiceActivityDetectionEnabled: Boolean
|
|
163
|
+
get() = DeviceStore.get("glasses", "voiceActivityDetectionEnabled") as? Boolean ?: true
|
|
160
164
|
|
|
161
165
|
val batteryLevel: Int
|
|
162
166
|
get() = DeviceStore.get("glasses", "batteryLevel") as? Int ?: -1
|
|
@@ -14,6 +14,7 @@ class Simulated : SGCManager() {
|
|
|
14
14
|
DeviceStore.apply("glasses", "connected", true)
|
|
15
15
|
DeviceStore.apply("glasses", "connectionState", ConnTypes.CONNECTED)
|
|
16
16
|
DeviceStore.apply("glasses", "micEnabled", false)
|
|
17
|
+
DeviceStore.apply("glasses", "voiceActivityDetectionEnabled", true)
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
// Audio Control
|
|
@@ -34,7 +35,8 @@ class Simulated : SGCManager() {
|
|
|
34
35
|
authToken: String?,
|
|
35
36
|
compress: String?,
|
|
36
37
|
flash: Boolean,
|
|
37
|
-
sound: Boolean
|
|
38
|
+
sound: Boolean,
|
|
39
|
+
exposureTimeNs: Long?,
|
|
38
40
|
) {
|
|
39
41
|
Bridge.log("requestPhoto flash=$flash, sound=$sound")
|
|
40
42
|
}
|