@mentra/bluetooth-sdk 0.1.20 → 0.1.21-beta.1
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 +27 -12
- package/android/build.gradle +3 -0
- package/android/src/main/AndroidManifest.xml +3 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkAnalytics.kt +34 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkDefaults.kt +1 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +169 -26
- package/android/src/main/java/com/mentra/bluetoothsdk/Bridge.kt +72 -16
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +127 -8
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +4 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +400 -99
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraLocalNetworkModule.kt +333 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/OtaManifest.kt +43 -3
- package/android/src/main/java/com/mentra/bluetoothsdk/camera/CameraModels.kt +18 -2
- package/android/src/main/java/com/mentra/bluetoothsdk/debug/BleTraceLogger.kt +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/services/Foreground.kt +53 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Ar99.java +2653 -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 +342 -42
- 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 +7 -2
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Simulated.kt +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/ar99/ota/Ar99OtaManager.java +566 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/ar99/ota/OtaByteUtils.java +18 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/ar99/ota/OtaCommandConstants.java +35 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/ar99/ota/OtaCrc32Util.java +13 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/ar99/ota/OtaGattTransport.java +11 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/ar99/ota/OtaProtocol.java +92 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/status/WifiHotspotStatus.kt +19 -3
- package/android/src/main/java/com/mentra/bluetoothsdk/streaming/StreamModels.kt +52 -2
- package/android/src/main/java/com/mentra/bluetoothsdk/tts/TTSTools.kt +115 -47
- package/android/src/main/java/com/mentra/bluetoothsdk/types/DeviceModels.kt +7 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/BleJsonCompact.java +8 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/Constants.kt +2 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/PhoneAudioMonitor.kt +45 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/audio/Ar99OpusPcmDecoder.java +398 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/audio/PcmStreamPlayer.kt +328 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/OtaManifestDowngradeTest.kt +86 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/StreamStatusEventTest.kt +29 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/camera/PhotoRequestTest.kt +45 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/Ar99AdvertisementMatcherTest.java +22 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/utils/BleJsonCompactTest.java +20 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/utils/PhoneAudioMonitorTest.kt +45 -0
- package/build/BluetoothSdk.types.d.ts +100 -22
- package/build/BluetoothSdk.types.d.ts.map +1 -1
- package/build/BluetoothSdk.types.js +2 -1
- package/build/BluetoothSdk.types.js.map +1 -1
- package/build/_internal.d.ts +3 -0
- package/build/_internal.d.ts.map +1 -1
- package/build/_internal.js +3 -0
- package/build/_internal.js.map +1 -1
- package/build/_private/BluetoothSdkModule.d.ts +31 -4
- package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
- package/build/_private/BluetoothSdkModule.js +26 -3
- 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 +1 -1
- package/build/_private/cameraRequestPayload.d.ts.map +1 -1
- package/build/_private/cameraRequestPayload.js +7 -0
- package/build/_private/cameraRequestPayload.js.map +1 -1
- package/build/_private/photoRequestPayload.d.ts.map +1 -1
- package/build/_private/photoRequestPayload.js +10 -0
- package/build/_private/photoRequestPayload.js.map +1 -1
- package/build/_private/sdkOtaManifest.d.ts +20 -0
- package/build/_private/sdkOtaManifest.d.ts.map +1 -0
- package/build/_private/sdkOtaManifest.js +28 -0
- package/build/_private/sdkOtaManifest.js.map +1 -0
- package/build/index.d.ts +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +11 -1
- package/build/index.js.map +1 -1
- package/expo-module.config.json +1 -0
- package/ios/BluetoothSdkModule.swift +113 -2
- package/ios/Source/BluetoothSdkDefaults.swift +2 -1
- package/ios/Source/Bridge.swift +87 -21
- package/ios/Source/DeviceManager.swift +118 -20
- package/ios/Source/DeviceStore.swift +4 -0
- package/ios/Source/MentraBluetoothSDK.swift +326 -33
- package/ios/Source/OtaManifest.swift +47 -4
- package/ios/Source/audio/PcmStreamPlayer.swift +410 -0
- package/ios/Source/camera/CameraModels.swift +26 -6
- package/ios/Source/internal/BleTraceLogger.swift +1 -1
- package/ios/Source/internal/BluetoothSdkAnalytics.swift +39 -0
- package/ios/Source/sgcs/Ar99.swift +2451 -0
- 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 +478 -61
- package/ios/Source/sgcs/MentraNex.swift +1 -1
- package/ios/Source/sgcs/Nimo.swift +1 -1
- package/ios/Source/sgcs/SGCManager.swift +13 -2
- package/ios/Source/sgcs/Simulated.swift +1 -1
- package/ios/Source/sgcs/ar99/ota/Ar99OtaManager.swift +637 -0
- package/ios/Source/status/WifiHotspotStatus.swift +21 -2
- package/ios/Source/streaming/StreamModels.swift +68 -2
- package/ios/Source/tts/TTSTools.swift +1 -1
- package/ios/Source/types/DeviceModels.swift +13 -0
- package/ios/Source/utils/BleJsonCompact.swift +8 -0
- package/ios/Source/utils/Constants.swift +2 -0
- package/ios/Source/utils/PhoneAudioMonitor.swift +4 -0
- package/package.json +1 -2
- package/src/BluetoothSdk.types.ts +106 -24
- package/src/_internal.ts +3 -0
- package/src/_private/BluetoothSdkModule.ts +83 -18
- package/src/_private/MentraLocalNetworkModule.ts +50 -0
- package/src/_private/cameraRequestPayload.ts +9 -2
- package/src/_private/photoRequestPayload.ts +13 -4
- package/src/_private/sdkOtaManifest.ts +31 -0
- package/src/index.ts +13 -1
|
@@ -34,6 +34,7 @@ import com.mentra.bluetoothsdk.Bridge
|
|
|
34
34
|
import com.mentra.bluetoothsdk.DeviceManager
|
|
35
35
|
import com.mentra.bluetoothsdk.PhotoRequest
|
|
36
36
|
import com.mentra.bluetoothsdk.PhotoSize
|
|
37
|
+
import com.mentra.bluetoothsdk.PhotoMode
|
|
37
38
|
import com.mentra.bluetoothsdk.DeviceStore
|
|
38
39
|
import com.mentra.bluetoothsdk.ObservableStore
|
|
39
40
|
import com.mentra.bluetoothsdk.debug.BleTraceLogger
|
|
@@ -94,6 +95,7 @@ class MentraLive : SGCManager() {
|
|
|
94
95
|
// LC3 frame size for Mentra Live
|
|
95
96
|
private const val LC3_FRAME_SIZE = 40
|
|
96
97
|
private const val VOICE_ACTIVITY_DETECTION_SWITCH_TYPE = 8
|
|
98
|
+
private const val LOUDNESS_GATE_SWITCH_TYPE = 10
|
|
97
99
|
private const val BES2700_MTU_LIMIT = 509
|
|
98
100
|
|
|
99
101
|
// L2CAP CoC fast path: new BES2700 firmware registers an LE L2CAP CoC server on this
|
|
@@ -146,6 +148,10 @@ class MentraLive : SGCManager() {
|
|
|
146
148
|
|
|
147
149
|
// Heartbeat parameters
|
|
148
150
|
private const val HEARTBEAT_INTERVAL_MS = 30000 // 30 seconds
|
|
151
|
+
// Grace period before an unanswered BLE wire v2 handshake is re-armed for retry,
|
|
152
|
+
// and the per-session cap on automatic retries (see sendWireHandshake).
|
|
153
|
+
private const val WIRE_HANDSHAKE_RETRY_GRACE_MS = 5000L
|
|
154
|
+
private const val WIRE_HANDSHAKE_MAX_ATTEMPTS = 3
|
|
149
155
|
private const val BATTERY_REQUEST_EVERY_N_HEARTBEATS = 10 // Every 10 heartbeats (5 minutes)
|
|
150
156
|
private const val RSSI_READ_INTERVAL_MS = 10000L // 10 seconds
|
|
151
157
|
|
|
@@ -213,12 +219,30 @@ class MentraLive : SGCManager() {
|
|
|
213
219
|
private var peerWireProtocolVersion = 0
|
|
214
220
|
private var useBinaryWireProtocol = false
|
|
215
221
|
private var wireHandshakeQueued = false
|
|
222
|
+
private var wireHandshakeAttempts = 0
|
|
223
|
+
// Session generation in which the LAST outgoing v2 handshake was sent. A handshake
|
|
224
|
+
// reply only activates v2 when it answers a handshake from the CURRENT epoch - a
|
|
225
|
+
// stale handshake that survived in flight across an epoch reset must not flip the
|
|
226
|
+
// new session to v2 before it negotiated its own build and capabilities.
|
|
227
|
+
private var wireHandshakeSentGeneration = -1
|
|
228
|
+
// Bumped on every BLE session reset; scheduled handshake-retry callbacks capture it
|
|
229
|
+
// and no-op if the session changed, so a timer from a dead session can't poke a new one.
|
|
230
|
+
private var wireSessionGeneration = 0
|
|
216
231
|
// Negotiated K900 STRING length endianness for the phone<->glasses BLE link. Defaults to
|
|
217
232
|
// legacy big-endian; upgraded to little-endian only when the glasses advertise wire_caps.k900_le
|
|
218
233
|
// (or a v2 binary handshake succeeds, which implies wire-v2 LE).
|
|
219
234
|
private var peerK900Le = false
|
|
220
235
|
private var peerWireCapsBinary = false
|
|
221
236
|
private var peerFilePayloadV2 = false
|
|
237
|
+
// Last observed glasses process session id (`sid` in glasses_ready / version_info_1).
|
|
238
|
+
// The BES keeps the BLE link alive across asg_client restarts, so transport state
|
|
239
|
+
// cannot signal a restart - a CHANGED (or newly appearing) sid is the restart signal.
|
|
240
|
+
// Null = no sid observed this BLE session (legacy glasses, or none seen yet).
|
|
241
|
+
private var glassesSessionId: String? = null
|
|
242
|
+
// True once a glasses_ready completed on THIS physical BLE session; resets only with
|
|
243
|
+
// the physical connection (never on heartbeat readiness flaps), so a first-seen sid
|
|
244
|
+
// after an upgrade OTA is always detected as a restart.
|
|
245
|
+
private var readinessCompletedThisBleSession = false
|
|
222
246
|
// Note: appVersion, buildNumber, deviceModel, androidVersion
|
|
223
247
|
// are inherited from SGCManager parent class
|
|
224
248
|
|
|
@@ -313,6 +337,8 @@ class MentraLive : SGCManager() {
|
|
|
313
337
|
// So we temporarily suspend the LC3 mic during phone audio playback
|
|
314
338
|
private var micIntentEnabled = false // User/system WANTS mic enabled
|
|
315
339
|
private var micSuspendedForAudio = false // Mic temporarily suspended due to phone audio
|
|
340
|
+
override val isMicSuspendedForAudio: Boolean
|
|
341
|
+
get() = micSuspendedForAudio
|
|
316
342
|
private var phoneAudioMonitor: PhoneAudioMonitor? = null
|
|
317
343
|
private var micOnCount = 0
|
|
318
344
|
private var micOffCount = 0
|
|
@@ -780,18 +806,25 @@ class MentraLive : SGCManager() {
|
|
|
780
806
|
val isEqual = state == connectionState
|
|
781
807
|
if (isEqual) {
|
|
782
808
|
if (state == ConnTypes.DISCONNECTED) {
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
peerFilePayloadV2 = false
|
|
790
|
-
BleJsonCompact.resetSession()
|
|
809
|
+
resetWireNegotiationState()
|
|
810
|
+
// Queued writes are session-bound: transmitting them into the NEXT session
|
|
811
|
+
// (e.g. a stale handshake whose reply activates v2 before the new session
|
|
812
|
+
// negotiated) is the bug class this clear removes. Higher layers re-send
|
|
813
|
+
// what still matters via their own ACK/retry tracking.
|
|
814
|
+
sendQueue.clear()
|
|
791
815
|
}
|
|
792
816
|
return
|
|
793
817
|
}
|
|
794
818
|
|
|
819
|
+
if (state == ConnTypes.DISCONNECTED) {
|
|
820
|
+
// A manufacturing serial is session-bound. Clear it on disconnect so a previous
|
|
821
|
+
// pair's serial can never be associated with the next connection. Connect must NOT
|
|
822
|
+
// clear it: DeviceManager.disconnect already wipes it before any new connection, and
|
|
823
|
+
// clearing on CONNECTED would wipe a still-valid serial mid-session when a same-link
|
|
824
|
+
// glasses_ready (e.g. ASG restart) re-publishes CONNECTED.
|
|
825
|
+
DeviceStore.apply("glasses", "serialNumber", "")
|
|
826
|
+
}
|
|
827
|
+
|
|
795
828
|
// Actually update the connection state!
|
|
796
829
|
DeviceStore.apply("glasses", "connectionState", state)
|
|
797
830
|
|
|
@@ -807,6 +840,10 @@ class MentraLive : SGCManager() {
|
|
|
807
840
|
DeviceStore.apply("glasses", "appVersion", "")
|
|
808
841
|
DeviceStore.apply("glasses", "besFirmwareVersion", "")
|
|
809
842
|
DeviceStore.apply("glasses", "mtkFirmwareVersion", "")
|
|
843
|
+
// Modern ASG builds omit ota_version_url entirely (the phone owns manifest
|
|
844
|
+
// selection), and the chunked parser only writes present fields — clear it here so
|
|
845
|
+
// a URL reported by a previous build cannot leak into this session.
|
|
846
|
+
DeviceStore.apply("glasses", "otaVersionUrl", "")
|
|
810
847
|
Bridge.log("LIVE: Cleared cached version_info fields for fresh session")
|
|
811
848
|
}
|
|
812
849
|
|
|
@@ -815,14 +852,9 @@ class MentraLive : SGCManager() {
|
|
|
815
852
|
DeviceStore.apply("glasses", "connected", false)
|
|
816
853
|
DeviceStore.apply("glasses", "signalStrength", -1)
|
|
817
854
|
DeviceStore.apply("glasses", "signalStrengthUpdatedAt", 0L)
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
wireHandshakeQueued = false
|
|
822
|
-
peerK900Le = false
|
|
823
|
-
peerWireCapsBinary = false
|
|
824
|
-
peerFilePayloadV2 = false
|
|
825
|
-
BleJsonCompact.resetSession()
|
|
855
|
+
resetWireNegotiationState()
|
|
856
|
+
sendQueue.clear() // see the disconnect reset above: stale writes die with the session
|
|
857
|
+
|
|
826
858
|
// Drop OTA caches when fully disconnected — avoids leaking session/step state
|
|
827
859
|
// from a previous pairing into the next one.
|
|
828
860
|
resetOtaCache()
|
|
@@ -1552,6 +1584,8 @@ class MentraLive : SGCManager() {
|
|
|
1552
1584
|
|
|
1553
1585
|
connectedDevice = null
|
|
1554
1586
|
glassesReady = false // Reset ready state on disconnect
|
|
1587
|
+
glassesSessionId = null // Fresh BLE session starts with no sid known
|
|
1588
|
+
readinessCompletedThisBleSession = false
|
|
1555
1589
|
|
|
1556
1590
|
// Reset audio pairing flags
|
|
1557
1591
|
glassesReadyReceived = false
|
|
@@ -1616,6 +1650,8 @@ class MentraLive : SGCManager() {
|
|
|
1616
1650
|
isConnected = false
|
|
1617
1651
|
isConnecting = false
|
|
1618
1652
|
glassesReady = false
|
|
1653
|
+
glassesSessionId = null
|
|
1654
|
+
readinessCompletedThisBleSession = false
|
|
1619
1655
|
glassesReadyReceived = false
|
|
1620
1656
|
audioConnected = false
|
|
1621
1657
|
|
|
@@ -3136,7 +3172,12 @@ class MentraLive : SGCManager() {
|
|
|
3136
3172
|
DeviceStore.apply("glasses", "wifiError", "")
|
|
3137
3173
|
}
|
|
3138
3174
|
|
|
3139
|
-
updateWifiStatus(
|
|
3175
|
+
updateWifiStatus(
|
|
3176
|
+
wifiConnectedStatus,
|
|
3177
|
+
ssid,
|
|
3178
|
+
localIp,
|
|
3179
|
+
wifiError.takeIf { it.isNotEmpty() }
|
|
3180
|
+
)
|
|
3140
3181
|
}
|
|
3141
3182
|
"hotspot_status_update" -> {
|
|
3142
3183
|
// Process hotspot status information (same pattern as "wifi_status")
|
|
@@ -3239,7 +3280,8 @@ class MentraLive : SGCManager() {
|
|
|
3239
3280
|
|
|
3240
3281
|
val scanComplete =
|
|
3241
3282
|
json.optBoolean("scan_complete", json.optBoolean("scanComplete", false))
|
|
3242
|
-
|
|
3283
|
+
val scanId = json.optString("scanId", "").ifEmpty { null }
|
|
3284
|
+
Bridge.updateWifiScanResults(networks, scanComplete, scanId)
|
|
3243
3285
|
}
|
|
3244
3286
|
"token_status" -> {
|
|
3245
3287
|
// Process coreToken acknowledgment
|
|
@@ -3492,8 +3534,19 @@ class MentraLive : SGCManager() {
|
|
|
3492
3534
|
// Glasses SOC has booted and is ready for communication
|
|
3493
3535
|
Bridge.log("LIVE: 🎉 Received glasses_ready message - SOC is booted and ready!")
|
|
3494
3536
|
|
|
3495
|
-
//
|
|
3537
|
+
// glasses_ready is a REMOTE wire-session reset: the glasses ran
|
|
3538
|
+
// onTransportReset() before sending it, so their side is back on legacy
|
|
3539
|
+
// framing regardless of what this side negotiated earlier. Start a fresh
|
|
3540
|
+
// wire epoch (clears v2-active state that would otherwise gate the
|
|
3541
|
+
// handshake off), then negotiate from the caps this message advertises.
|
|
3542
|
+
resetWireNegotiationState()
|
|
3496
3543
|
parsePeerWireCaps(json)
|
|
3544
|
+
maybeSendWireHandshake()
|
|
3545
|
+
// Record the session id BEFORE marking ready: an unsolicited glasses_ready
|
|
3546
|
+
// already runs this full remote-reset flow, so recording (not re-triggering)
|
|
3547
|
+
// is correct here; version_info detection covers the restart case.
|
|
3548
|
+
json.optString("sid", "").takeIf { it.isNotEmpty() }?.let { glassesSessionId = it }
|
|
3549
|
+
readinessCompletedThisBleSession = true
|
|
3497
3550
|
|
|
3498
3551
|
// Set the ready flag to stop any future readiness checks
|
|
3499
3552
|
glassesReady = true
|
|
@@ -3653,7 +3706,7 @@ class MentraLive : SGCManager() {
|
|
|
3653
3706
|
|
|
3654
3707
|
"version_info" -> {
|
|
3655
3708
|
// Process version information from ASG client (legacy single-message format)
|
|
3656
|
-
Bridge.log("LIVE: Received version info from ASG client
|
|
3709
|
+
Bridge.log("LIVE: Received version info from ASG client")
|
|
3657
3710
|
|
|
3658
3711
|
// Extract version information
|
|
3659
3712
|
val appVersionLegacy = json.optString("app_version", "")
|
|
@@ -3663,6 +3716,7 @@ class MentraLive : SGCManager() {
|
|
|
3663
3716
|
val otaVersionUrlLegacy: String? = json.optString("ota_version_url", null)
|
|
3664
3717
|
val firmwareVersionLegacy = json.optString("firmware_version", "")
|
|
3665
3718
|
val btMacAddressLegacy = json.optString("bt_mac_address", "")
|
|
3719
|
+
val serialNumberLegacy = json.optString("serial_number", "")
|
|
3666
3720
|
|
|
3667
3721
|
// Update parent SGCManager fields
|
|
3668
3722
|
DeviceStore.apply("glasses", "appVersion", appVersionLegacy)
|
|
@@ -3676,6 +3730,9 @@ class MentraLive : SGCManager() {
|
|
|
3676
3730
|
)
|
|
3677
3731
|
DeviceStore.apply("glasses", "firmwareVersion", firmwareVersionLegacy)
|
|
3678
3732
|
DeviceStore.apply("glasses", "bluetoothMacAddress", btMacAddressLegacy)
|
|
3733
|
+
if (serialNumberLegacy.isNotBlank()) {
|
|
3734
|
+
DeviceStore.apply("glasses", "serialNumber", serialNumberLegacy)
|
|
3735
|
+
}
|
|
3679
3736
|
|
|
3680
3737
|
val versionInfoLegacy = HashMap<String, Any>()
|
|
3681
3738
|
versionInfoLegacy["appVersion"] = appVersionLegacy
|
|
@@ -3857,7 +3914,7 @@ class MentraLive : SGCManager() {
|
|
|
3857
3914
|
else -> {
|
|
3858
3915
|
// Flexible version_info parsing - handle any version_info* message
|
|
3859
3916
|
if (type.startsWith("version_info")) {
|
|
3860
|
-
Bridge.log("LIVE: Received " + type
|
|
3917
|
+
Bridge.log("LIVE: Received " + type)
|
|
3861
3918
|
|
|
3862
3919
|
// Extract all fields from JSON (except "type")
|
|
3863
3920
|
val fields = HashMap<String, Any>()
|
|
@@ -3884,8 +3941,6 @@ class MentraLive : SGCManager() {
|
|
|
3884
3941
|
val buildNumInt = Integer.parseInt(buildNum)
|
|
3885
3942
|
buildNumberInt = buildNumInt
|
|
3886
3943
|
Bridge.log("LIVE: Parsed build number as integer: " + buildNumInt)
|
|
3887
|
-
parsePeerWireCaps(json)
|
|
3888
|
-
maybeSendWireHandshake()
|
|
3889
3944
|
} catch (e: NumberFormatException) {
|
|
3890
3945
|
buildNumberInt = 0
|
|
3891
3946
|
Log.e(TAG, "Failed to parse build number as integer: " + buildNum)
|
|
@@ -3946,6 +4001,10 @@ class MentraLive : SGCManager() {
|
|
|
3946
4001
|
fields["bt_mac_address"] as String
|
|
3947
4002
|
)
|
|
3948
4003
|
}
|
|
4004
|
+
val serialNumber = fields["serial_number"] as? String
|
|
4005
|
+
if (!serialNumber.isNullOrBlank()) {
|
|
4006
|
+
DeviceStore.apply("glasses", "serialNumber", serialNumber)
|
|
4007
|
+
}
|
|
3949
4008
|
if (fields.containsKey("system_time_ms")) {
|
|
3950
4009
|
val v = fields["system_time_ms"]
|
|
3951
4010
|
if (v is Number) {
|
|
@@ -3953,6 +4012,17 @@ class MentraLive : SGCManager() {
|
|
|
3953
4012
|
}
|
|
3954
4013
|
}
|
|
3955
4014
|
|
|
4015
|
+
// Negotiate wire caps from ANY version_info chunk, not only the one
|
|
4016
|
+
// carrying build_number: the glasses put wire_caps in version_info_3
|
|
4017
|
+
// (firmware chunk) while build_number travels in version_info_1, so
|
|
4018
|
+
// gating negotiation on build_number silently discards the caps.
|
|
4019
|
+
// parsePeerWireCaps no-ops without a wire_caps key and
|
|
4020
|
+
// maybeSendWireHandshake gates on caps + build + not-yet-queued, so
|
|
4021
|
+
// running them per chunk is safe.
|
|
4022
|
+
parsePeerWireCaps(json)
|
|
4023
|
+
maybeSendWireHandshake()
|
|
4024
|
+
handleGlassesSessionId(json)
|
|
4025
|
+
|
|
3956
4026
|
Bridge.log("LIVE: Processed version_info fields and sent to RN")
|
|
3957
4027
|
Bridge.sendVersionInfo(fields)
|
|
3958
4028
|
} else {
|
|
@@ -4303,15 +4373,7 @@ class MentraLive : SGCManager() {
|
|
|
4303
4373
|
// The glassesReady flag is reset on disconnect/reconnect, so this won't
|
|
4304
4374
|
// prevent proper reconnection
|
|
4305
4375
|
if (!glassesReady) {
|
|
4306
|
-
|
|
4307
|
-
"LIVE: 📱 Sending phone_ready to glasses - waiting for glasses_ready response"
|
|
4308
|
-
)
|
|
4309
|
-
val readyMsg = JSONObject()
|
|
4310
|
-
readyMsg.put("type", "phone_ready")
|
|
4311
|
-
readyMsg.put("timestamp", System.currentTimeMillis())
|
|
4312
|
-
|
|
4313
|
-
// Send it through our data channel
|
|
4314
|
-
sendJson(readyMsg, true)
|
|
4376
|
+
sendPhoneReady("SOC ready")
|
|
4315
4377
|
} else {
|
|
4316
4378
|
Bridge.log(
|
|
4317
4379
|
"LIVE: ✅ Glasses already marked as ready, skipping phone_ready"
|
|
@@ -4392,6 +4454,8 @@ class MentraLive : SGCManager() {
|
|
|
4392
4454
|
updateConnectionState(ConnTypes.DISCONNECTED)
|
|
4393
4455
|
glassesReady = false
|
|
4394
4456
|
glassesReadyReceived = false
|
|
4457
|
+
glassesSessionId = null
|
|
4458
|
+
readinessCompletedThisBleSession = false
|
|
4395
4459
|
}
|
|
4396
4460
|
"sr_adota" -> {
|
|
4397
4461
|
// BES chip OTA progress — K900 path (serial busy during BES flash). Emit ota_status
|
|
@@ -4457,7 +4521,20 @@ class MentraLive : SGCManager() {
|
|
|
4457
4521
|
|
|
4458
4522
|
val syntheticStatus: String
|
|
4459
4523
|
if ("FINISHED" == besOtaStatus) {
|
|
4460
|
-
|
|
4524
|
+
// The glasses power-cycle right after the final BES tick, so a
|
|
4525
|
+
// session whose BES step is the LAST step never gets a follow-up
|
|
4526
|
+
// ota_status from the glasses — consumers mapping on this synthetic
|
|
4527
|
+
// status would otherwise never see a terminal state. Emit "complete"
|
|
4528
|
+
// for the final step; mid-session BES steps keep "step_complete" so
|
|
4529
|
+
// session-level trackers advance normally. Unknown sessions
|
|
4530
|
+
// (cachedOtaTotalSteps == 0, e.g. legacy glasses that never sent an
|
|
4531
|
+
// ota_status) conservatively keep "step_complete".
|
|
4532
|
+
syntheticStatus =
|
|
4533
|
+
if (cachedOtaTotalSteps > 0 &&
|
|
4534
|
+
cachedOtaCurrentStep >= cachedOtaTotalSteps
|
|
4535
|
+
)
|
|
4536
|
+
"complete"
|
|
4537
|
+
else "step_complete"
|
|
4461
4538
|
} else if ("FAILED" == besOtaStatus) {
|
|
4462
4539
|
syntheticStatus = "failed"
|
|
4463
4540
|
} else {
|
|
@@ -4701,7 +4778,12 @@ class MentraLive : SGCManager() {
|
|
|
4701
4778
|
/**
|
|
4702
4779
|
* Update WiFi status and notify listeners Matches iOS MentraLive.swift updateWifiStatus pattern
|
|
4703
4780
|
*/
|
|
4704
|
-
private fun updateWifiStatus(
|
|
4781
|
+
private fun updateWifiStatus(
|
|
4782
|
+
connected: Boolean,
|
|
4783
|
+
ssid: String,
|
|
4784
|
+
localIp: String,
|
|
4785
|
+
error: String? = null
|
|
4786
|
+
) {
|
|
4705
4787
|
Bridge.log("LIVE: 🌐 Updating WiFi status - connected: " + connected + ", SSID: " + ssid)
|
|
4706
4788
|
|
|
4707
4789
|
// Update parent SGCManager fields
|
|
@@ -4710,7 +4792,7 @@ class MentraLive : SGCManager() {
|
|
|
4710
4792
|
DeviceStore.apply("glasses", "wifiLocalIp", localIp)
|
|
4711
4793
|
|
|
4712
4794
|
// Send event to bridge for cloud communication
|
|
4713
|
-
Bridge.sendWifiStatusChange(connected, ssid, localIp)
|
|
4795
|
+
Bridge.sendWifiStatusChange(connected, ssid, localIp, error)
|
|
4714
4796
|
}
|
|
4715
4797
|
|
|
4716
4798
|
/**
|
|
@@ -4784,10 +4866,13 @@ class MentraLive : SGCManager() {
|
|
|
4784
4866
|
/**
|
|
4785
4867
|
* Request WiFi scan from the glasses This will ask the glasses to scan for available networks
|
|
4786
4868
|
*/
|
|
4787
|
-
override fun requestWifiScan() {
|
|
4869
|
+
override fun requestWifiScan(scanId: String?) {
|
|
4788
4870
|
try {
|
|
4789
4871
|
val json = JSONObject()
|
|
4790
4872
|
json.put("type", "request_wifi_scan")
|
|
4873
|
+
if (!scanId.isNullOrEmpty()) {
|
|
4874
|
+
json.put("scanId", scanId)
|
|
4875
|
+
}
|
|
4791
4876
|
sendJson(json, true)
|
|
4792
4877
|
Bridge.log("LIVE: Sending WiFi scan request to glasses")
|
|
4793
4878
|
} catch (e: JSONException) {
|
|
@@ -5458,8 +5543,9 @@ class MentraLive : SGCManager() {
|
|
|
5458
5543
|
val bleImgId = "I" + String.format("%09d", System.currentTimeMillis() % 1000000000)
|
|
5459
5544
|
json.put("bleImgId", bleImgId)
|
|
5460
5545
|
|
|
5461
|
-
//
|
|
5462
|
-
|
|
5546
|
+
// Miniapps may explicitly skip direct Wi-Fi upload and force the
|
|
5547
|
+
// phone-relayed BLE path. Auto remains the default.
|
|
5548
|
+
json.put("transferMethod", request.transferMethod)
|
|
5463
5549
|
|
|
5464
5550
|
// Always prepare for potential BLE transfer
|
|
5465
5551
|
if (webhookUrl != null && !webhookUrl.isEmpty()) {
|
|
@@ -5469,7 +5555,7 @@ class MentraLive : SGCManager() {
|
|
|
5469
5555
|
blePhotoTransfers[bleImgId] = transfer
|
|
5470
5556
|
}
|
|
5471
5557
|
|
|
5472
|
-
Bridge.log("LIVE: Using
|
|
5558
|
+
Bridge.log("LIVE: Using " + request.transferMethod + " transfer mode with BLE fallback ID: " + bleImgId)
|
|
5473
5559
|
Bridge.log(
|
|
5474
5560
|
"LIVE: PHOTO PIPELINE [5b/6] JSON ready mode=" +
|
|
5475
5561
|
mode +
|
|
@@ -5488,14 +5574,19 @@ class MentraLive : SGCManager() {
|
|
|
5488
5574
|
fun warmUpCamera(
|
|
5489
5575
|
requestId: String,
|
|
5490
5576
|
size: PhotoSize,
|
|
5577
|
+
mode: PhotoMode = PhotoMode.PHOTO,
|
|
5491
5578
|
exposureTimeNs: Long?,
|
|
5492
5579
|
durationMs: Int,
|
|
5580
|
+
zsl: Boolean? = null,
|
|
5581
|
+
mfnr: Boolean? = null,
|
|
5493
5582
|
) {
|
|
5494
5583
|
Bridge.log(
|
|
5495
5584
|
"LIVE: warmUpCamera() entry — requestId=" +
|
|
5496
5585
|
requestId +
|
|
5497
5586
|
", size=" +
|
|
5498
5587
|
size.value +
|
|
5588
|
+
", mode=" +
|
|
5589
|
+
mode.value +
|
|
5499
5590
|
", durationMs=" +
|
|
5500
5591
|
durationMs
|
|
5501
5592
|
)
|
|
@@ -5506,9 +5597,16 @@ class MentraLive : SGCManager() {
|
|
|
5506
5597
|
json.put("requestId", requestId)
|
|
5507
5598
|
val sizeValue = size.value
|
|
5508
5599
|
json.put("size", if (sizeValue.isNotEmpty()) sizeValue else "medium")
|
|
5600
|
+
json.put("mode", mode.value)
|
|
5509
5601
|
if (exposureTimeNs != null && exposureTimeNs > 0L) {
|
|
5510
5602
|
json.put("exposureTimeNs", exposureTimeNs)
|
|
5511
5603
|
}
|
|
5604
|
+
if (mfnr != null) {
|
|
5605
|
+
json.put("mfnr", mfnr)
|
|
5606
|
+
}
|
|
5607
|
+
if (zsl != null) {
|
|
5608
|
+
json.put("zsl", zsl)
|
|
5609
|
+
}
|
|
5512
5610
|
json.put("durationMs", if (durationMs > 0) durationMs else 15000)
|
|
5513
5611
|
sendJson(json, true)
|
|
5514
5612
|
} catch (e: JSONException) {
|
|
@@ -5516,6 +5614,16 @@ class MentraLive : SGCManager() {
|
|
|
5516
5614
|
}
|
|
5517
5615
|
}
|
|
5518
5616
|
|
|
5617
|
+
fun stopCameraWarmUp(requestId: String) {
|
|
5618
|
+
if (!isConnected) {
|
|
5619
|
+
throw IllegalStateException("not_connected")
|
|
5620
|
+
}
|
|
5621
|
+
val json = JSONObject()
|
|
5622
|
+
json.put("type", "camera_warm_up_stop")
|
|
5623
|
+
json.put("requestId", requestId)
|
|
5624
|
+
sendJson(json, true)
|
|
5625
|
+
}
|
|
5626
|
+
|
|
5519
5627
|
override fun startStream(message: MutableMap<String, Any>) {
|
|
5520
5628
|
Bridge.log("LIVE: Starting RTMP stream")
|
|
5521
5629
|
|
|
@@ -7162,6 +7270,84 @@ class MentraLive : SGCManager() {
|
|
|
7162
7270
|
* negotiated per-link endianness and binary support. Missing wire_caps leaves the legacy
|
|
7163
7271
|
* defaults (BE, no binary) untouched so older glasses keep working.
|
|
7164
7272
|
*/
|
|
7273
|
+
/**
|
|
7274
|
+
* Drop every negotiated wire-session artifact and bump the session generation so
|
|
7275
|
+
* scheduled callbacks from the old session stand down. Called on BLE disconnect and on
|
|
7276
|
+
* glasses_ready: the glasses run onTransportReset() before sending glasses_ready, so a
|
|
7277
|
+
* mid-link ASG restart (no BLE disconnect) resets THEIR side to legacy framing - the
|
|
7278
|
+
* phone must treat every glasses_ready as a new wire epoch and renegotiate (bounded by
|
|
7279
|
+
* the per-session handshake attempt cap) instead of trusting stale v2-active state.
|
|
7280
|
+
*/
|
|
7281
|
+
private fun resetWireNegotiationState() {
|
|
7282
|
+
incomingChunkReassembler.clear()
|
|
7283
|
+
peerWireProtocolVersion = 0
|
|
7284
|
+
useBinaryWireProtocol = false
|
|
7285
|
+
wireHandshakeQueued = false
|
|
7286
|
+
wireHandshakeAttempts = 0
|
|
7287
|
+
wireSessionGeneration++
|
|
7288
|
+
peerK900Le = false
|
|
7289
|
+
peerWireCapsBinary = false
|
|
7290
|
+
peerFilePayloadV2 = false
|
|
7291
|
+
BleJsonCompact.resetSession()
|
|
7292
|
+
wireHandshakeSentGeneration = -1
|
|
7293
|
+
}
|
|
7294
|
+
|
|
7295
|
+
/** Sends phone_ready to (re)start the glasses-driven readiness flow. */
|
|
7296
|
+
private fun sendPhoneReady(reason: String) {
|
|
7297
|
+
Bridge.log("LIVE: 📱 Sending phone_ready to glasses ($reason) - waiting for glasses_ready response")
|
|
7298
|
+
val readyMsg = JSONObject()
|
|
7299
|
+
readyMsg.put("type", "phone_ready")
|
|
7300
|
+
readyMsg.put("timestamp", System.currentTimeMillis())
|
|
7301
|
+
sendJson(readyMsg, true)
|
|
7302
|
+
}
|
|
7303
|
+
|
|
7304
|
+
/**
|
|
7305
|
+
* Tracks the glasses process session id (`sid`) and re-runs the phone-driven readiness
|
|
7306
|
+
* flow when it changes under a live BLE link. The BES holds the link across asg_client
|
|
7307
|
+
* restarts (APK OTA, crash recovery), so a restarted glasses process is invisible to
|
|
7308
|
+
* transport state; the sid is the explicit protocol signal.
|
|
7309
|
+
*
|
|
7310
|
+
* Tri-state by design (retro-compat with pre-sid glasses builds):
|
|
7311
|
+
* - no `sid` key: legacy glasses - do nothing, today's behavior;
|
|
7312
|
+
* - sid appears where none was known on an ESTABLISHED session: the glasses just
|
|
7313
|
+
* updated from a pre-sid build and restarted - treat as a restart (this is the
|
|
7314
|
+
* upgrade OTA itself);
|
|
7315
|
+
* - sid differs from the recorded one: the glasses restarted - treat as a restart;
|
|
7316
|
+
* - same sid: same process, no action.
|
|
7317
|
+
*
|
|
7318
|
+
* A restart is handled as a LOGICAL session reset: send phone_ready immediately
|
|
7319
|
+
* (instead of waiting for the next sr_hrt heartbeat, and bypassing its stale
|
|
7320
|
+
* glassesReady suppression); the returning glasses_ready runs the full existing
|
|
7321
|
+
* remote-wire-reset flow. glassesReady/fullyBooted are deliberately left untouched -
|
|
7322
|
+
* the physical link never dropped, so the connection UI must not flap. The RN layer
|
|
7323
|
+
* is notified so the OTA coordinator can treat it as its reconnect edge.
|
|
7324
|
+
*/
|
|
7325
|
+
private fun handleGlassesSessionId(json: JSONObject) {
|
|
7326
|
+
val sid = json.optString("sid", "")
|
|
7327
|
+
if (sid.isEmpty()) return
|
|
7328
|
+
val previous = glassesSessionId
|
|
7329
|
+
if (sid == previous) return
|
|
7330
|
+
glassesSessionId = sid
|
|
7331
|
+
if (previous == null && !readinessCompletedThisBleSession) {
|
|
7332
|
+
// First sid of a fresh BLE session before readiness completes: the normal
|
|
7333
|
+
// pairing/readiness flow is already running - just record it.
|
|
7334
|
+
return
|
|
7335
|
+
}
|
|
7336
|
+
Bridge.log(
|
|
7337
|
+
"LIVE: 🔁 Glasses session changed (${previous ?: "<pre-sid build>"} -> $sid) - " +
|
|
7338
|
+
"asg restarted under a live link, re-running readiness"
|
|
7339
|
+
)
|
|
7340
|
+
sendPhoneReady("glasses session changed")
|
|
7341
|
+
try {
|
|
7342
|
+
val payload = HashMap<String, Any>()
|
|
7343
|
+
payload["previous_sid"] = previous ?: ""
|
|
7344
|
+
payload["sid"] = sid
|
|
7345
|
+
Bridge.sendTypedMessage("glasses_session_changed", payload)
|
|
7346
|
+
} catch (e: Exception) {
|
|
7347
|
+
Log.e(TAG, "Error emitting glasses_session_changed", e)
|
|
7348
|
+
}
|
|
7349
|
+
}
|
|
7350
|
+
|
|
7165
7351
|
private fun parsePeerWireCaps(json: JSONObject) {
|
|
7166
7352
|
val caps = json.optJSONObject("wire_caps") ?: return
|
|
7167
7353
|
if (caps.optBoolean("k900_le", false)) {
|
|
@@ -7233,7 +7419,39 @@ class MentraLive : SGCManager() {
|
|
|
7233
7419
|
)
|
|
7234
7420
|
Bridge.log("LIVE: Sending BLE wire v2 handshake")
|
|
7235
7421
|
wireHandshakeQueued = true
|
|
7422
|
+
wireHandshakeSentGeneration = wireSessionGeneration
|
|
7236
7423
|
queueData(packed, null)
|
|
7424
|
+
// The handshake and its reply are fire-and-forget binary frames with no ACK
|
|
7425
|
+
// tracking; if either is lost, wireHandshakeQueued would block every future
|
|
7426
|
+
// attempt and the session would silently stay on the legacy string wire.
|
|
7427
|
+
// Re-arm after a grace period so negotiation can retry, bounded per session
|
|
7428
|
+
// so a peer that advertises binary but never answers doesn't get pinged
|
|
7429
|
+
// forever (later caps/version triggers may still retry explicitly).
|
|
7430
|
+
wireHandshakeAttempts++
|
|
7431
|
+
val scheduledGeneration = wireSessionGeneration
|
|
7432
|
+
handler.postDelayed(
|
|
7433
|
+
{
|
|
7434
|
+
if (scheduledGeneration == wireSessionGeneration &&
|
|
7435
|
+
wireHandshakeQueued &&
|
|
7436
|
+
peerWireProtocolVersion < BleWireProtocol.PROTOCOL_V2
|
|
7437
|
+
) {
|
|
7438
|
+
Bridge.log(
|
|
7439
|
+
"LIVE: BLE wire v2 handshake unanswered after " +
|
|
7440
|
+
WIRE_HANDSHAKE_RETRY_GRACE_MS +
|
|
7441
|
+
"ms (attempt " +
|
|
7442
|
+
wireHandshakeAttempts +
|
|
7443
|
+
"/" +
|
|
7444
|
+
WIRE_HANDSHAKE_MAX_ATTEMPTS +
|
|
7445
|
+
") - re-arming"
|
|
7446
|
+
)
|
|
7447
|
+
wireHandshakeQueued = false
|
|
7448
|
+
if (wireHandshakeAttempts < WIRE_HANDSHAKE_MAX_ATTEMPTS) {
|
|
7449
|
+
maybeSendWireHandshake()
|
|
7450
|
+
}
|
|
7451
|
+
}
|
|
7452
|
+
},
|
|
7453
|
+
WIRE_HANDSHAKE_RETRY_GRACE_MS
|
|
7454
|
+
)
|
|
7237
7455
|
} catch (e: Exception) {
|
|
7238
7456
|
Log.e(TAG, "Failed to send wire handshake", e)
|
|
7239
7457
|
}
|
|
@@ -7243,12 +7461,17 @@ class MentraLive : SGCManager() {
|
|
|
7243
7461
|
peerWireProtocolVersion = BleWireProtocol.PROTOCOL_V2
|
|
7244
7462
|
useBinaryWireProtocol = true
|
|
7245
7463
|
wireHandshakeQueued = false
|
|
7464
|
+
wireHandshakeAttempts = 0
|
|
7246
7465
|
peerK900Le = true
|
|
7247
7466
|
BleJsonCompact.markSessionConnected(System.currentTimeMillis())
|
|
7248
7467
|
Bridge.log(logMessage)
|
|
7249
7468
|
}
|
|
7250
7469
|
|
|
7251
7470
|
private fun handlePeerWireHandshake() {
|
|
7471
|
+
if (wireHandshakeSentGeneration != wireSessionGeneration) {
|
|
7472
|
+
Bridge.log("LIVE: Ignoring wire v2 handshake reply from a previous session epoch")
|
|
7473
|
+
return
|
|
7474
|
+
}
|
|
7252
7475
|
activateBinaryWireV2Session("LIVE: Peer confirmed BLE wire protocol v2")
|
|
7253
7476
|
}
|
|
7254
7477
|
|
|
@@ -7947,7 +8170,7 @@ class MentraLive : SGCManager() {
|
|
|
7947
8170
|
val type = json.optString("type", "")
|
|
7948
8171
|
|
|
7949
8172
|
when (type) {
|
|
7950
|
-
"request_wifi_scan" -> requestWifiScan()
|
|
8173
|
+
"request_wifi_scan" -> requestWifiScan(null)
|
|
7951
8174
|
"rgb_led_control_on", "rgb_led_control_off" -> {
|
|
7952
8175
|
// Forward LED control commands directly to glasses via BLE
|
|
7953
8176
|
Log.d(TAG, "💡 Forwarding LED control command to glasses: " + type)
|
|
@@ -8763,6 +8986,9 @@ class MentraLive : SGCManager() {
|
|
|
8763
8986
|
|
|
8764
8987
|
// Send glasses-side Voice Activity Detection setting.
|
|
8765
8988
|
sendVoiceActivityDetectionSetting()
|
|
8989
|
+
|
|
8990
|
+
// Send glasses-side loudness / Barrier gate setting.
|
|
8991
|
+
sendLoudnessGateSetting()
|
|
8766
8992
|
}
|
|
8767
8993
|
|
|
8768
8994
|
override fun sendVoiceActivityDetectionSetting() {
|
|
@@ -8805,11 +9031,56 @@ class MentraLive : SGCManager() {
|
|
|
8805
9031
|
}
|
|
8806
9032
|
}
|
|
8807
9033
|
|
|
9034
|
+
override fun sendLoudnessGateSetting() {
|
|
9035
|
+
val value = DeviceStore.get("bluetooth", "loudness_gate_enabled")
|
|
9036
|
+
val enabled =
|
|
9037
|
+
if (value is Boolean) value
|
|
9038
|
+
else BluetoothSdkDefaults.LOUDNESS_GATE_ENABLED
|
|
9039
|
+
|
|
9040
|
+
Bridge.log("LIVE: 🎚️ Sending loudness/Barrier gate setting to glasses: " + enabled)
|
|
9041
|
+
|
|
9042
|
+
if (!isConnected) {
|
|
9043
|
+
Bridge.log("LIVE: Cannot send loudness gate setting - not connected")
|
|
9044
|
+
return
|
|
9045
|
+
}
|
|
9046
|
+
|
|
9047
|
+
try {
|
|
9048
|
+
val body = JSONObject()
|
|
9049
|
+
body.put("type", LOUDNESS_GATE_SWITCH_TYPE)
|
|
9050
|
+
body.put("switch", if (enabled) 1 else 0)
|
|
9051
|
+
|
|
9052
|
+
val cmdObject = JSONObject()
|
|
9053
|
+
cmdObject.put("C", "cs_swit")
|
|
9054
|
+
cmdObject.put("V", 1)
|
|
9055
|
+
cmdObject.put("B", body.toString())
|
|
9056
|
+
|
|
9057
|
+
val packedData =
|
|
9058
|
+
K900ProtocolUtils.packDataToK900(
|
|
9059
|
+
cmdObject.toString().toByteArray(StandardCharsets.UTF_8),
|
|
9060
|
+
K900ProtocolUtils.CMD_TYPE_STRING,
|
|
9061
|
+
k900LengthEndian()
|
|
9062
|
+
)
|
|
9063
|
+
if (packedData == null) {
|
|
9064
|
+
Bridge.log("LIVE: Failed to pack loudness gate setting command")
|
|
9065
|
+
return
|
|
9066
|
+
}
|
|
9067
|
+
queueData(packedData)
|
|
9068
|
+
} catch (e: JSONException) {
|
|
9069
|
+
Log.e(TAG, "Error creating loudness gate setting command", e)
|
|
9070
|
+
}
|
|
9071
|
+
}
|
|
9072
|
+
|
|
8808
9073
|
/** Send button photo settings to glasses, replaying all stored scan-tuning fields. */
|
|
8809
9074
|
override fun sendButtonPhotoSettings() {
|
|
9075
|
+
val legacyZslMfnr =
|
|
9076
|
+
DeviceStore.get(ObservableStore.BLUETOOTH_CATEGORY, "button_photo_zsl_mfnr") as Boolean?
|
|
8810
9077
|
val size = DeviceStore.get(ObservableStore.BLUETOOTH_CATEGORY, "button_photo_size") as String?
|
|
8811
|
-
val mfnr =
|
|
8812
|
-
|
|
9078
|
+
val mfnr =
|
|
9079
|
+
(DeviceStore.get(ObservableStore.BLUETOOTH_CATEGORY, "button_photo_mfnr") as Boolean?)
|
|
9080
|
+
?: legacyZslMfnr
|
|
9081
|
+
val zsl =
|
|
9082
|
+
(DeviceStore.get(ObservableStore.BLUETOOTH_CATEGORY, "button_photo_zsl") as Boolean?)
|
|
9083
|
+
?: legacyZslMfnr
|
|
8813
9084
|
val noiseReduction = DeviceStore.get(ObservableStore.BLUETOOTH_CATEGORY, "button_photo_noise_reduction") as Boolean?
|
|
8814
9085
|
val edgeEnhancement = DeviceStore.get(ObservableStore.BLUETOOTH_CATEGORY, "button_photo_edge_enhancement") as Boolean?
|
|
8815
9086
|
val ispDigitalGain = DeviceStore.get(ObservableStore.BLUETOOTH_CATEGORY, "button_photo_isp_digital_gain") as Int?
|
|
@@ -8868,6 +9139,35 @@ class MentraLive : SGCManager() {
|
|
|
8868
9139
|
}
|
|
8869
9140
|
}
|
|
8870
9141
|
|
|
9142
|
+
fun sendCameraFovOverride(
|
|
9143
|
+
requestId: String,
|
|
9144
|
+
leaseId: String,
|
|
9145
|
+
fov: Int,
|
|
9146
|
+
roiPosition: Int,
|
|
9147
|
+
ttlMs: Int,
|
|
9148
|
+
) {
|
|
9149
|
+
val json = JSONObject()
|
|
9150
|
+
json.put("type", "camera_fov_override")
|
|
9151
|
+
json.put("request_id", requestId)
|
|
9152
|
+
json.put(
|
|
9153
|
+
"params",
|
|
9154
|
+
JSONObject()
|
|
9155
|
+
.put("lease_id", leaseId)
|
|
9156
|
+
.put("fov", fov)
|
|
9157
|
+
.put("roi_position", roiPosition)
|
|
9158
|
+
.put("ttl_ms", ttlMs),
|
|
9159
|
+
)
|
|
9160
|
+
sendJson(json, true)
|
|
9161
|
+
}
|
|
9162
|
+
|
|
9163
|
+
fun releaseCameraFovOverride(requestId: String, leaseId: String) {
|
|
9164
|
+
val json = JSONObject()
|
|
9165
|
+
json.put("type", "camera_fov_override_release")
|
|
9166
|
+
json.put("request_id", requestId)
|
|
9167
|
+
json.put("params", JSONObject().put("lease_id", leaseId))
|
|
9168
|
+
sendJson(json, true)
|
|
9169
|
+
}
|
|
9170
|
+
|
|
8871
9171
|
/**
|
|
8872
9172
|
* Send camera tuning config (ANR / gain) to the glasses via the {@code camera_tuning_config}
|
|
8873
9173
|
* command. The ASG client relays this as a {@code camconfig} broadcast to the camera HAL.
|