@mentra/bluetooth-sdk 0.1.17 → 0.1.19
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 +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/Bridge.kt +52 -4
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +119 -16
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +0 -6
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +22 -21
- package/android/src/main/java/com/mentra/bluetoothsdk/OtaManifest.kt +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G2.kt +488 -15
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.kt +457 -31
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLiveL2capChannel.kt +274 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraNex.kt +361 -67
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraosBle.java +7501 -3704
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/SGCManager.kt +134 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/status/DeviceStatus.kt +0 -6
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/BleJsonCompact.java +493 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/BlePhotoUploadService.java +4 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/BleWireProtocol.java +108 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/IncidentLogBleUploadService.java +4 -3
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/K900LengthCodec.java +115 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/K900ProtocolUtils.java +103 -68
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/MessageChunkReassembler.java +106 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/MessageChunker.java +93 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/NexSGCUtils.kt +111 -11
- package/android/src/test/java/com/mentra/bluetoothsdk/camera/PhotoRequestTest.kt +7 -9
- package/android/src/test/java/com/mentra/bluetoothsdk/utils/AvifExifStripperTest.java +17 -1
- package/android/src/test/java/com/mentra/bluetoothsdk/utils/BinaryMessageChunkerTest.java +84 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/utils/BleJsonCompactTest.java +158 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/utils/K900ProtocolUtilsEndiannessTest.java +172 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/utils/K900ProtocolUtilsTest.java +24 -0
- package/build/BluetoothSdk.types.d.ts +12 -1
- package/build/BluetoothSdk.types.d.ts.map +1 -1
- package/build/BluetoothSdk.types.js.map +1 -1
- package/build/types/index.d.ts +3 -0
- package/build/types/index.d.ts.map +1 -0
- package/build/types/index.js +2 -0
- package/build/types/index.js.map +1 -0
- package/ios/LocalPhotoUploadServer.swift +78 -0
- package/ios/MentraPhotoReceiverModule.swift +10 -0
- package/ios/Source/BluetoothSdkDefaults.swift +1 -1
- package/ios/Source/Bridge.swift +63 -0
- package/ios/Source/DeviceManager.swift +134 -29
- package/ios/Source/DeviceStore.swift +0 -5
- package/ios/Source/MentraBluetoothSDK.swift +26 -23
- package/ios/Source/OtaManifest.swift +1 -1
- package/ios/Source/internal/BleTraceLogger.swift +192 -0
- package/ios/Source/sgcs/G2.swift +518 -11
- package/ios/Source/sgcs/MentraLive.swift +607 -39
- package/ios/Source/sgcs/MentraNex.swift +416 -32
- package/ios/Source/sgcs/SGCManager.swift +155 -0
- package/ios/Source/sgcs/mentraos_ble.pb.swift +726 -122
- package/ios/Source/status/DeviceStatus.swift +0 -8
- package/ios/Source/utils/BleJsonCompact.swift +395 -0
- package/ios/Source/utils/BleWireProtocol.swift +92 -0
- package/ios/Source/utils/MessageChunkReassembler.swift +82 -0
- package/ios/Source/utils/MessageChunker.swift +73 -0
- package/package.json +14 -7
- package/src/BluetoothSdk.types.ts +13 -1
- package/src/types/index.ts +7 -0
|
@@ -41,6 +41,9 @@ import com.mentra.bluetoothsdk.utils.ConnTypes
|
|
|
41
41
|
import com.mentra.bluetoothsdk.utils.DeviceTypes
|
|
42
42
|
import com.mentra.bluetoothsdk.utils.IncidentLogBleRelayNaming
|
|
43
43
|
import com.mentra.bluetoothsdk.utils.IncidentLogBleUploadService
|
|
44
|
+
import com.mentra.bluetoothsdk.utils.BleJsonCompact
|
|
45
|
+
import com.mentra.bluetoothsdk.utils.BleWireProtocol
|
|
46
|
+
import com.mentra.bluetoothsdk.utils.K900LengthCodec
|
|
44
47
|
import com.mentra.bluetoothsdk.utils.K900ProtocolUtils
|
|
45
48
|
import com.mentra.bluetoothsdk.utils.MessageChunkReassembler
|
|
46
49
|
import com.mentra.bluetoothsdk.utils.MessageChunker
|
|
@@ -90,6 +93,12 @@ class MentraLive : SGCManager() {
|
|
|
90
93
|
// LC3 frame size for Mentra Live
|
|
91
94
|
private const val LC3_FRAME_SIZE = 40
|
|
92
95
|
private const val VOICE_ACTIVITY_DETECTION_SWITCH_TYPE = 8
|
|
96
|
+
private const val BES2700_MTU_LIMIT = 509
|
|
97
|
+
|
|
98
|
+
// L2CAP CoC fast path: new BES2700 firmware registers an LE L2CAP CoC server on this
|
|
99
|
+
// PSM. When the phone opens the channel, the glasses send file packets over it instead
|
|
100
|
+
// of GATT FILE_READ notifications. Phone→glasses traffic stays on GATT.
|
|
101
|
+
private const val L2CAP_FILE_PSM = 0x00C9
|
|
93
102
|
|
|
94
103
|
// BLE UUIDs - updated to match K900 BES2800 MCU UUIDs for compatibility with both glass
|
|
95
104
|
// types
|
|
@@ -199,6 +208,14 @@ class MentraLive : SGCManager() {
|
|
|
199
208
|
|
|
200
209
|
// Local-only fields (not in parent SGCManager)
|
|
201
210
|
private var buildNumberInt = 0 // Build number as integer for version checks
|
|
211
|
+
private var peerWireProtocolVersion = 0
|
|
212
|
+
private var useBinaryWireProtocol = false
|
|
213
|
+
private var wireHandshakeQueued = false
|
|
214
|
+
// Negotiated K900 STRING length endianness for the phone<->glasses BLE link. Defaults to
|
|
215
|
+
// legacy big-endian; upgraded to little-endian only when the glasses advertise wire_caps.k900_le
|
|
216
|
+
// (or a v2 binary handshake succeeds, which implies wire-v2 LE).
|
|
217
|
+
private var peerK900Le = false
|
|
218
|
+
private var peerWireCapsBinary = false
|
|
202
219
|
// Note: appVersion, buildNumber, deviceModel, androidVersion
|
|
203
220
|
// are inherited from SGCManager parent class
|
|
204
221
|
|
|
@@ -319,6 +336,12 @@ class MentraLive : SGCManager() {
|
|
|
319
336
|
private var fileReadNotificationCount = 0 // Debug counter for FILE_READ notifications
|
|
320
337
|
private val incomingChunkReassembler = MessageChunkReassembler()
|
|
321
338
|
|
|
339
|
+
// L2CAP CoC fast path for incoming file transfers (see L2CAP_FILE_PSM).
|
|
340
|
+
// The channel is read-only; all outgoing messages remain on GATT. When it can't be
|
|
341
|
+
// opened (older firmware, Android < 10), GATT notifications remain the file path.
|
|
342
|
+
private val enableL2capFilePath = true
|
|
343
|
+
private var l2capFileChannel: MentraLiveL2capChannel? = null
|
|
344
|
+
|
|
322
345
|
private val connectionLock = Any()
|
|
323
346
|
|
|
324
347
|
private var glassesMediaVolumeTimeoutRunnable: Runnable? = null
|
|
@@ -752,6 +775,12 @@ class MentraLive : SGCManager() {
|
|
|
752
775
|
if (isEqual) {
|
|
753
776
|
if (state == ConnTypes.DISCONNECTED) {
|
|
754
777
|
incomingChunkReassembler.clear()
|
|
778
|
+
peerWireProtocolVersion = 0
|
|
779
|
+
useBinaryWireProtocol = false
|
|
780
|
+
wireHandshakeQueued = false
|
|
781
|
+
peerK900Le = false
|
|
782
|
+
peerWireCapsBinary = false
|
|
783
|
+
BleJsonCompact.resetSession()
|
|
755
784
|
}
|
|
756
785
|
return
|
|
757
786
|
}
|
|
@@ -780,6 +809,12 @@ class MentraLive : SGCManager() {
|
|
|
780
809
|
DeviceStore.apply("glasses", "signalStrength", -1)
|
|
781
810
|
DeviceStore.apply("glasses", "signalStrengthUpdatedAt", 0L)
|
|
782
811
|
incomingChunkReassembler.clear()
|
|
812
|
+
peerWireProtocolVersion = 0
|
|
813
|
+
useBinaryWireProtocol = false
|
|
814
|
+
wireHandshakeQueued = false
|
|
815
|
+
peerK900Le = false
|
|
816
|
+
peerWireCapsBinary = false
|
|
817
|
+
BleJsonCompact.resetSession()
|
|
783
818
|
// Drop OTA caches when fully disconnected — avoids leaking session/step state
|
|
784
819
|
// from a previous pairing into the next one.
|
|
785
820
|
resetOtaCache()
|
|
@@ -1427,6 +1462,26 @@ class MentraLive : SGCManager() {
|
|
|
1427
1462
|
isConnecting = false
|
|
1428
1463
|
isConnected = true
|
|
1429
1464
|
connectedDevice = gatt.device
|
|
1465
|
+
|
|
1466
|
+
// High-priority connection interval (~11-15ms vs 30-50ms
|
|
1467
|
+
// default) and 2M PHY, as MentraNex and G2 already do. The
|
|
1468
|
+
// BES firmware only *prefers* 2M PHY; the central has to
|
|
1469
|
+
// request the switch or the link stays on 1M.
|
|
1470
|
+
try {
|
|
1471
|
+
gatt.requestConnectionPriority(
|
|
1472
|
+
BluetoothGatt.CONNECTION_PRIORITY_HIGH
|
|
1473
|
+
)
|
|
1474
|
+
gatt.setPreferredPhy(
|
|
1475
|
+
BluetoothDevice.PHY_LE_2M_MASK,
|
|
1476
|
+
BluetoothDevice.PHY_LE_2M_MASK,
|
|
1477
|
+
BluetoothDevice.PHY_OPTION_NO_PREFERRED
|
|
1478
|
+
)
|
|
1479
|
+
} catch (e: SecurityException) {
|
|
1480
|
+
Bridge.log(
|
|
1481
|
+
"LIVE: requestConnectionPriority/setPreferredPhy denied: ${e.message}"
|
|
1482
|
+
)
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1430
1485
|
DeviceStore.apply("glasses", "bluetoothName", connectedDevice!!.name)
|
|
1431
1486
|
// Persist MAC so reconnection can use direct GATT instead of scanning
|
|
1432
1487
|
if (connectedDevice!!.address != null) {
|
|
@@ -1523,6 +1578,9 @@ class MentraLive : SGCManager() {
|
|
|
1523
1578
|
// Stop micbeat mechanism
|
|
1524
1579
|
stopMicBeat()
|
|
1525
1580
|
|
|
1581
|
+
// Close the L2CAP file channel (if the fast path was open)
|
|
1582
|
+
closeL2capFileChannel()
|
|
1583
|
+
|
|
1526
1584
|
// Clean up GATT resources
|
|
1527
1585
|
closeGattQuietly(false)
|
|
1528
1586
|
|
|
@@ -1575,6 +1633,9 @@ class MentraLive : SGCManager() {
|
|
|
1575
1633
|
// Stop micbeat mechanism
|
|
1576
1634
|
stopMicBeat()
|
|
1577
1635
|
|
|
1636
|
+
// Close the L2CAP file channel (if the fast path was open)
|
|
1637
|
+
closeL2capFileChannel()
|
|
1638
|
+
|
|
1578
1639
|
// Clean up resources
|
|
1579
1640
|
closeGattQuietly(false)
|
|
1580
1641
|
|
|
@@ -2780,6 +2841,49 @@ class MentraLive : SGCManager() {
|
|
|
2780
2841
|
synchronized(filePacketBufferLock) { filePacketBufferSize = 0 }
|
|
2781
2842
|
}
|
|
2782
2843
|
|
|
2844
|
+
/**
|
|
2845
|
+
* Open the L2CAP CoC fast path for incoming file transfers (PSM 0x00C9).
|
|
2846
|
+
*
|
|
2847
|
+
* New BES2700 firmware registers an LE L2CAP CoC server; when the phone opens the channel,
|
|
2848
|
+
* the glasses send K900 file packets over it instead of GATT FILE_READ notifications.
|
|
2849
|
+
* Complete frames from the channel are fed into processFilePacketData — the exact same entry
|
|
2850
|
+
* point used by GATT FILE_READ notifications — so downstream reassembly (FileTransferSession,
|
|
2851
|
+
* transfer_complete) is unchanged. On any failure to open we stay on GATT notifications.
|
|
2852
|
+
*/
|
|
2853
|
+
private fun openL2capFileChannel() {
|
|
2854
|
+
if (!enableL2capFilePath) {
|
|
2855
|
+
return
|
|
2856
|
+
}
|
|
2857
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
|
2858
|
+
// createInsecureL2capChannel requires API 29
|
|
2859
|
+
Bridge.log("LIVE: L2CAP: unavailable, staying on GATT (requires Android 10+)")
|
|
2860
|
+
return
|
|
2861
|
+
}
|
|
2862
|
+
if (l2capFileChannel != null) {
|
|
2863
|
+
// Already open (or connecting) for this connection — e.g. repeated glasses_ready
|
|
2864
|
+
return
|
|
2865
|
+
}
|
|
2866
|
+
val device = connectedDevice
|
|
2867
|
+
if (device == null) {
|
|
2868
|
+
Log.w(TAG, "L2CAP: no connected device, staying on GATT")
|
|
2869
|
+
return
|
|
2870
|
+
}
|
|
2871
|
+
val channel =
|
|
2872
|
+
MentraLiveL2capChannel(L2CAP_FILE_PSM) { frame ->
|
|
2873
|
+
// Same code path as a GATT FILE_READ notification carrying this frame
|
|
2874
|
+
processFilePacketData(frame)
|
|
2875
|
+
}
|
|
2876
|
+
l2capFileChannel = channel
|
|
2877
|
+
channel.open(device)
|
|
2878
|
+
}
|
|
2879
|
+
|
|
2880
|
+
/** Close the L2CAP file channel if open (call on disconnect). */
|
|
2881
|
+
private fun closeL2capFileChannel() {
|
|
2882
|
+
val channel = l2capFileChannel ?: return
|
|
2883
|
+
l2capFileChannel = null
|
|
2884
|
+
channel.close()
|
|
2885
|
+
}
|
|
2886
|
+
|
|
2783
2887
|
/** Process data received from the glasses */
|
|
2784
2888
|
private fun processReceivedData(data: ByteArray?, size: Int) {
|
|
2785
2889
|
// Bridge.log("LIVE: Processing received data: " + bytesToHex(data));
|
|
@@ -2847,10 +2951,26 @@ class MentraLive : SGCManager() {
|
|
|
2847
2951
|
return // Exit after processing file packet
|
|
2848
2952
|
}
|
|
2849
2953
|
|
|
2954
|
+
if (cmdType == K900ProtocolUtils.CMD_TYPE_BINARY_MSG) {
|
|
2955
|
+
processBinaryWireFrame(data)
|
|
2956
|
+
return
|
|
2957
|
+
}
|
|
2958
|
+
|
|
2959
|
+
// Learn the glasses' K900 length endianness from the frame we just received so future
|
|
2960
|
+
// outbound frames match. Guards against a peer that never advertises wire_caps.
|
|
2961
|
+
K900LengthCodec.detectLength(data)?.let { detected ->
|
|
2962
|
+
peerK900Le = detected.endian == K900LengthCodec.Endian.LE
|
|
2963
|
+
}
|
|
2964
|
+
|
|
2850
2965
|
// Otherwise it's a normal JSON message
|
|
2851
2966
|
val json = K900ProtocolUtils.processReceivedBytesToJson(data)
|
|
2852
2967
|
if (json != null) {
|
|
2853
|
-
|
|
2968
|
+
val expanded = expandCompactWireJson(json)
|
|
2969
|
+
if (expanded == null) {
|
|
2970
|
+
Log.w(TAG, "Thread-$threadId: Rejected unsupported compact wire form")
|
|
2971
|
+
return
|
|
2972
|
+
}
|
|
2973
|
+
processJsonMessage(expanded)
|
|
2854
2974
|
} else {
|
|
2855
2975
|
Log.w(TAG, "Thread-" + threadId + ": Failed to parse K900 protocol data")
|
|
2856
2976
|
// #region agent log [810da2] Hypothesis A+B: log header-declared length vs actual
|
|
@@ -3035,10 +3155,12 @@ class MentraLive : SGCManager() {
|
|
|
3035
3155
|
)
|
|
3036
3156
|
"speaking_status" -> handleSpeakingStatus(json.optBoolean("speaking", false))
|
|
3037
3157
|
"battery_status" -> {
|
|
3038
|
-
//
|
|
3158
|
+
// Percent only. battery_status derives from hm_batv, which carries no charge
|
|
3159
|
+
// bit — older glasses fabricate `charging` from a voltage threshold (>3.9V)
|
|
3160
|
+
// that reads "charging" for most of a discharging pack's range. Charging
|
|
3161
|
+
// state comes exclusively from the PMU charg bit in the sr_hrt heartbeat.
|
|
3039
3162
|
val percent = json.optInt("percent", batteryLevel)
|
|
3040
|
-
|
|
3041
|
-
updateBatteryStatus(percent, charging)
|
|
3163
|
+
updateBatteryStatus(percent, isCharging)
|
|
3042
3164
|
}
|
|
3043
3165
|
"pong" ->
|
|
3044
3166
|
// Process heartbeat pong response
|
|
@@ -3057,6 +3179,18 @@ class MentraLive : SGCManager() {
|
|
|
3057
3179
|
val ssid = json.optString("ssid", "")
|
|
3058
3180
|
val localIp = json.optString("local_ip", "")
|
|
3059
3181
|
|
|
3182
|
+
// Provisioning failure reason (e.g. connect_timeout). Sticky: routine
|
|
3183
|
+
// error-less status updates (link-state debounce, request_wifi_status)
|
|
3184
|
+
// must not clear a failure nothing recovered from — only a newer error
|
|
3185
|
+
// or a successful connection overwrites it.
|
|
3186
|
+
val wifiError = json.optString("error", "")
|
|
3187
|
+
if (wifiError.isNotEmpty()) {
|
|
3188
|
+
Bridge.log("LIVE: 🌐 WiFi provisioning error from glasses: $wifiError")
|
|
3189
|
+
DeviceStore.apply("glasses", "wifiError", wifiError)
|
|
3190
|
+
} else if (wifiConnectedStatus) {
|
|
3191
|
+
DeviceStore.apply("glasses", "wifiError", "")
|
|
3192
|
+
}
|
|
3193
|
+
|
|
3060
3194
|
updateWifiStatus(wifiConnectedStatus, ssid, localIp)
|
|
3061
3195
|
}
|
|
3062
3196
|
"hotspot_status_update" -> {
|
|
@@ -3413,6 +3547,9 @@ class MentraLive : SGCManager() {
|
|
|
3413
3547
|
// Glasses SOC has booted and is ready for communication
|
|
3414
3548
|
Bridge.log("LIVE: 🎉 Received glasses_ready message - SOC is booted and ready!")
|
|
3415
3549
|
|
|
3550
|
+
// Negotiate wire capabilities advertised in the glasses_ready handshake.
|
|
3551
|
+
parsePeerWireCaps(json)
|
|
3552
|
+
|
|
3416
3553
|
// Set the ready flag to stop any future readiness checks
|
|
3417
3554
|
glassesReady = true
|
|
3418
3555
|
glassesReadyReceived = true
|
|
@@ -3423,11 +3560,16 @@ class MentraLive : SGCManager() {
|
|
|
3423
3560
|
// Stop the readiness check loop since we got confirmation
|
|
3424
3561
|
stopReadinessCheckLoop()
|
|
3425
3562
|
|
|
3563
|
+
// Try to open the L2CAP CoC fast path for file transfers. No-op when the
|
|
3564
|
+
// firmware doesn't support it — GATT notifications remain the default path.
|
|
3565
|
+
try {
|
|
3566
|
+
openL2capFileChannel()
|
|
3567
|
+
} catch (t: Throwable) {
|
|
3568
|
+
Bridge.log("LIVE: ⚠️ glasses_ready: openL2capFileChannel threw: " + t)
|
|
3569
|
+
}
|
|
3570
|
+
|
|
3426
3571
|
// Send BLE MTU config to glasses so they can adjust file packet sizes.
|
|
3427
|
-
|
|
3428
|
-
// BES2700 chip often ignores higher negotiated MTUs and truncates to 253 bytes,
|
|
3429
|
-
// but we should respect the actual negotiated value if it's lower.
|
|
3430
|
-
val BES2700_MTU_LIMIT = 256 // BES2700's known notification size limit
|
|
3572
|
+
// Use the minimum of negotiated MTU and BES2700's datapath limit (509).
|
|
3431
3573
|
val effectiveMtu = Math.min(currentMtu, BES2700_MTU_LIMIT)
|
|
3432
3574
|
Bridge.log(
|
|
3433
3575
|
"LIVE: 📦 Sending BLE MTU config: negotiated=" +
|
|
@@ -3607,6 +3749,8 @@ class MentraLive : SGCManager() {
|
|
|
3607
3749
|
buildNumberInt = 0
|
|
3608
3750
|
Log.e(TAG, "Failed to parse build number as integer: " + buildNumberLegacy)
|
|
3609
3751
|
}
|
|
3752
|
+
parsePeerWireCaps(json)
|
|
3753
|
+
maybeSendWireHandshake()
|
|
3610
3754
|
}
|
|
3611
3755
|
"ota_download_progress" -> {
|
|
3612
3756
|
// Process OTA download progress from ASG client
|
|
@@ -3792,8 +3936,12 @@ class MentraLive : SGCManager() {
|
|
|
3792
3936
|
// Parse build number as integer for version checks
|
|
3793
3937
|
try {
|
|
3794
3938
|
val buildNumInt = Integer.parseInt(buildNum)
|
|
3939
|
+
buildNumberInt = buildNumInt
|
|
3795
3940
|
Bridge.log("LIVE: Parsed build number as integer: " + buildNumInt)
|
|
3941
|
+
parsePeerWireCaps(json)
|
|
3942
|
+
maybeSendWireHandshake()
|
|
3796
3943
|
} catch (e: NumberFormatException) {
|
|
3944
|
+
buildNumberInt = 0
|
|
3797
3945
|
Log.e(TAG, "Failed to parse build number as integer: " + buildNum)
|
|
3798
3946
|
}
|
|
3799
3947
|
}
|
|
@@ -4253,11 +4401,10 @@ class MentraLive : SGCManager() {
|
|
|
4253
4401
|
"%"
|
|
4254
4402
|
)
|
|
4255
4403
|
|
|
4256
|
-
//
|
|
4257
|
-
//
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
// Update battery status using the existing method
|
|
4404
|
+
// Percent only. sr_batv carries just voltage+percent; inferring
|
|
4405
|
+
// charging from voltage (>4.0V) reads "not charging" for most of a
|
|
4406
|
+
// genuinely-charging pack's range. Charging state comes exclusively
|
|
4407
|
+
// from the PMU charg bit in the sr_hrt heartbeat.
|
|
4261
4408
|
updateBatteryStatus(batteryPercentage, isCharging)
|
|
4262
4409
|
}
|
|
4263
4410
|
} catch (e: Exception) {
|
|
@@ -4561,6 +4708,11 @@ class MentraLive : SGCManager() {
|
|
|
4561
4708
|
* pattern
|
|
4562
4709
|
*/
|
|
4563
4710
|
private fun updateBatteryStatus(level: Int, isCharging: Boolean) {
|
|
4711
|
+
// Keep the field in sync: percent-only messages (battery_status/sr_batv) re-pass
|
|
4712
|
+
// it as the last-known charging state, so a stale field would clobber the value
|
|
4713
|
+
// the sr_hrt PMU charg bit established.
|
|
4714
|
+
this.isCharging = isCharging
|
|
4715
|
+
|
|
4564
4716
|
// Update parent SGCManager fields
|
|
4565
4717
|
DeviceStore.apply("glasses", "batteryLevel", level)
|
|
4566
4718
|
DeviceStore.apply("glasses", "charging", isCharging)
|
|
@@ -6043,6 +6195,9 @@ class MentraLive : SGCManager() {
|
|
|
6043
6195
|
// sendRgbLedControlAuthority(false);
|
|
6044
6196
|
// }
|
|
6045
6197
|
|
|
6198
|
+
// Close the L2CAP file channel (if the fast path was open)
|
|
6199
|
+
closeL2capFileChannel()
|
|
6200
|
+
|
|
6046
6201
|
// Disconnect from GATT if connected
|
|
6047
6202
|
closeGattQuietly(true)
|
|
6048
6203
|
|
|
@@ -6366,7 +6521,8 @@ class MentraLive : SGCManager() {
|
|
|
6366
6521
|
val packedData =
|
|
6367
6522
|
K900ProtocolUtils.packDataToK900(
|
|
6368
6523
|
jsonStr.toByteArray(StandardCharsets.UTF_8),
|
|
6369
|
-
K900ProtocolUtils.CMD_TYPE_STRING
|
|
6524
|
+
K900ProtocolUtils.CMD_TYPE_STRING,
|
|
6525
|
+
k900LengthEndian()
|
|
6370
6526
|
)
|
|
6371
6527
|
|
|
6372
6528
|
queueData(packedData)
|
|
@@ -6550,7 +6706,8 @@ class MentraLive : SGCManager() {
|
|
|
6550
6706
|
val packedData =
|
|
6551
6707
|
K900ProtocolUtils.packDataToK900(
|
|
6552
6708
|
jsonStr.toByteArray(StandardCharsets.UTF_8),
|
|
6553
|
-
K900ProtocolUtils.CMD_TYPE_STRING
|
|
6709
|
+
K900ProtocolUtils.CMD_TYPE_STRING,
|
|
6710
|
+
k900LengthEndian()
|
|
6554
6711
|
)
|
|
6555
6712
|
queueData(packedData)
|
|
6556
6713
|
} catch (e: JSONException) {
|
|
@@ -6579,7 +6736,8 @@ class MentraLive : SGCManager() {
|
|
|
6579
6736
|
val packedData =
|
|
6580
6737
|
K900ProtocolUtils.packDataToK900(
|
|
6581
6738
|
jsonStr.toByteArray(StandardCharsets.UTF_8),
|
|
6582
|
-
K900ProtocolUtils.CMD_TYPE_STRING
|
|
6739
|
+
K900ProtocolUtils.CMD_TYPE_STRING,
|
|
6740
|
+
k900LengthEndian()
|
|
6583
6741
|
)
|
|
6584
6742
|
queueData(packedData)
|
|
6585
6743
|
} catch (e: JSONException) {
|
|
@@ -6653,7 +6811,8 @@ class MentraLive : SGCManager() {
|
|
|
6653
6811
|
val packedData =
|
|
6654
6812
|
K900ProtocolUtils.packDataToK900(
|
|
6655
6813
|
jsonStr.toByteArray(StandardCharsets.UTF_8),
|
|
6656
|
-
K900ProtocolUtils.CMD_TYPE_STRING
|
|
6814
|
+
K900ProtocolUtils.CMD_TYPE_STRING,
|
|
6815
|
+
k900LengthEndian()
|
|
6657
6816
|
)
|
|
6658
6817
|
Bridge.log("LIVE: AUDIO: Sending cs_getvol command: " + jsonStr)
|
|
6659
6818
|
queueData(packedData)
|
|
@@ -6677,7 +6836,8 @@ class MentraLive : SGCManager() {
|
|
|
6677
6836
|
val packedData =
|
|
6678
6837
|
K900ProtocolUtils.packDataToK900(
|
|
6679
6838
|
jsonStr.toByteArray(StandardCharsets.UTF_8),
|
|
6680
|
-
K900ProtocolUtils.CMD_TYPE_STRING
|
|
6839
|
+
K900ProtocolUtils.CMD_TYPE_STRING,
|
|
6840
|
+
k900LengthEndian()
|
|
6681
6841
|
)
|
|
6682
6842
|
queueData(packedData)
|
|
6683
6843
|
return true
|
|
@@ -7039,6 +7199,168 @@ class MentraLive : SGCManager() {
|
|
|
7039
7199
|
}
|
|
7040
7200
|
}
|
|
7041
7201
|
|
|
7202
|
+
/** Negotiated K900 STRING length endianness for outbound frames to the glasses. */
|
|
7203
|
+
private fun k900LengthEndian(): K900LengthCodec.Endian =
|
|
7204
|
+
if (peerK900Le) K900LengthCodec.Endian.LE else K900LengthCodec.Endian.BE
|
|
7205
|
+
|
|
7206
|
+
/**
|
|
7207
|
+
* Parse a {@code wire_caps} object from a version_info/glasses_ready message and update the
|
|
7208
|
+
* negotiated per-link endianness and binary support. Missing wire_caps leaves the legacy
|
|
7209
|
+
* defaults (BE, no binary) untouched so older glasses keep working.
|
|
7210
|
+
*/
|
|
7211
|
+
private fun parsePeerWireCaps(json: JSONObject) {
|
|
7212
|
+
val caps = json.optJSONObject("wire_caps") ?: return
|
|
7213
|
+
if (caps.optBoolean("k900_le", false)) {
|
|
7214
|
+
if (!peerK900Le) {
|
|
7215
|
+
peerK900Le = true
|
|
7216
|
+
Bridge.log("LIVE: wire_caps negotiated k900 endian=LE")
|
|
7217
|
+
}
|
|
7218
|
+
}
|
|
7219
|
+
if (caps.has("binary")) {
|
|
7220
|
+
peerWireCapsBinary = caps.optBoolean("binary", false)
|
|
7221
|
+
}
|
|
7222
|
+
}
|
|
7223
|
+
|
|
7224
|
+
private fun maybeSendWireHandshake() {
|
|
7225
|
+
// Only attempt the v2 binary handshake once the glasses have advertised binary support via
|
|
7226
|
+
// wire_caps. Older builds that report build>=5 but lack wire_caps stay on the legacy path.
|
|
7227
|
+
if (buildNumberInt < 5 ||
|
|
7228
|
+
!peerWireCapsBinary ||
|
|
7229
|
+
wireHandshakeQueued ||
|
|
7230
|
+
peerWireProtocolVersion >= BleWireProtocol.PROTOCOL_V2
|
|
7231
|
+
) {
|
|
7232
|
+
return
|
|
7233
|
+
}
|
|
7234
|
+
sendWireHandshake()
|
|
7235
|
+
}
|
|
7236
|
+
|
|
7237
|
+
private fun sendWireHandshake() {
|
|
7238
|
+
if (buildNumberInt < 5) {
|
|
7239
|
+
return
|
|
7240
|
+
}
|
|
7241
|
+
try {
|
|
7242
|
+
val payload = BleWireProtocol.HANDSHAKE_PAYLOAD_V2.toByteArray(StandardCharsets.UTF_8)
|
|
7243
|
+
var flags = BleWireProtocol.BLE_WIRE_FLAG_HANDSHAKE.toInt()
|
|
7244
|
+
flags = flags or BleWireProtocol.BLE_WIRE_FLAG_FIRST_FRAG.toInt()
|
|
7245
|
+
flags = flags or BleWireProtocol.BLE_WIRE_FLAG_LAST_FRAG.toInt()
|
|
7246
|
+
val packed =
|
|
7247
|
+
K900ProtocolUtils.packBinaryFragment(
|
|
7248
|
+
flags.toByte(),
|
|
7249
|
+
0,
|
|
7250
|
+
0,
|
|
7251
|
+
1,
|
|
7252
|
+
payload
|
|
7253
|
+
)
|
|
7254
|
+
Bridge.log("LIVE: Sending BLE wire v2 handshake")
|
|
7255
|
+
wireHandshakeQueued = true
|
|
7256
|
+
queueData(packed, null)
|
|
7257
|
+
} catch (e: Exception) {
|
|
7258
|
+
Log.e(TAG, "Failed to send wire handshake", e)
|
|
7259
|
+
}
|
|
7260
|
+
}
|
|
7261
|
+
|
|
7262
|
+
private fun activateBinaryWireV2Session(logMessage: String) {
|
|
7263
|
+
peerWireProtocolVersion = BleWireProtocol.PROTOCOL_V2
|
|
7264
|
+
useBinaryWireProtocol = true
|
|
7265
|
+
wireHandshakeQueued = false
|
|
7266
|
+
peerK900Le = true
|
|
7267
|
+
BleJsonCompact.markSessionConnected(System.currentTimeMillis())
|
|
7268
|
+
Bridge.log(logMessage)
|
|
7269
|
+
}
|
|
7270
|
+
|
|
7271
|
+
private fun handlePeerWireHandshake() {
|
|
7272
|
+
activateBinaryWireV2Session("LIVE: Peer confirmed BLE wire protocol v2")
|
|
7273
|
+
}
|
|
7274
|
+
|
|
7275
|
+
private fun processBinaryWireFrame(data: ByteArray) {
|
|
7276
|
+
val info = K900ProtocolUtils.extractBinaryFragmentInfo(data) ?: run {
|
|
7277
|
+
Log.w(TAG, "Failed to parse binary wire frame")
|
|
7278
|
+
return
|
|
7279
|
+
}
|
|
7280
|
+
|
|
7281
|
+
if (BleWireProtocol.isHandshakeV2(info)) {
|
|
7282
|
+
handlePeerWireHandshake()
|
|
7283
|
+
return
|
|
7284
|
+
}
|
|
7285
|
+
|
|
7286
|
+
if (!useBinaryWireProtocol && buildNumberInt >= 5) {
|
|
7287
|
+
activateBinaryWireV2Session(
|
|
7288
|
+
"LIVE: Auto-enabled BLE wire v2 from incoming binary frame"
|
|
7289
|
+
)
|
|
7290
|
+
}
|
|
7291
|
+
|
|
7292
|
+
val reassembled =
|
|
7293
|
+
incomingChunkReassembler.addBinaryFragment(
|
|
7294
|
+
info.msgId,
|
|
7295
|
+
info.fragIdx,
|
|
7296
|
+
info.fragCount,
|
|
7297
|
+
info.payload
|
|
7298
|
+
)
|
|
7299
|
+
?: return
|
|
7300
|
+
|
|
7301
|
+
try {
|
|
7302
|
+
val jsonStr = String(reassembled, StandardCharsets.UTF_8)
|
|
7303
|
+
val json = expandCompactWireJson(JSONObject(jsonStr))
|
|
7304
|
+
if (json == null) {
|
|
7305
|
+
Log.w(TAG, "Rejected unsupported compact reassembled wire form")
|
|
7306
|
+
return
|
|
7307
|
+
}
|
|
7308
|
+
logWireMetrics(
|
|
7309
|
+
reassembled.size,
|
|
7310
|
+
data.size,
|
|
7311
|
+
info.fragCount,
|
|
7312
|
+
BleWireProtocol.PROTOCOL_V2,
|
|
7313
|
+
"glasses_to_phone"
|
|
7314
|
+
)
|
|
7315
|
+
processJsonMessage(json)
|
|
7316
|
+
} catch (e: JSONException) {
|
|
7317
|
+
Log.e(TAG, "Failed to parse reassembled binary wire JSON", e)
|
|
7318
|
+
}
|
|
7319
|
+
}
|
|
7320
|
+
|
|
7321
|
+
private fun compactWireJson(json: JSONObject): JSONObject {
|
|
7322
|
+
if (!useBinaryWireProtocol || buildNumberInt < 5) {
|
|
7323
|
+
return json
|
|
7324
|
+
}
|
|
7325
|
+
return try {
|
|
7326
|
+
BleJsonCompact.encode(json)
|
|
7327
|
+
} catch (_: JSONException) {
|
|
7328
|
+
json
|
|
7329
|
+
}
|
|
7330
|
+
}
|
|
7331
|
+
|
|
7332
|
+
private fun expandCompactWireJson(json: JSONObject): JSONObject? {
|
|
7333
|
+
if (!useBinaryWireProtocol || buildNumberInt < 5) {
|
|
7334
|
+
return json
|
|
7335
|
+
}
|
|
7336
|
+
return try {
|
|
7337
|
+
BleJsonCompact.decodeIfSupported(json)
|
|
7338
|
+
} catch (_: JSONException) {
|
|
7339
|
+
json
|
|
7340
|
+
}
|
|
7341
|
+
}
|
|
7342
|
+
|
|
7343
|
+
private fun logWireMetrics(
|
|
7344
|
+
payloadBytes: Int,
|
|
7345
|
+
wireBytes: Int,
|
|
7346
|
+
packetCount: Int,
|
|
7347
|
+
protocolVersion: Int,
|
|
7348
|
+
direction: String
|
|
7349
|
+
) {
|
|
7350
|
+
Bridge.log(
|
|
7351
|
+
"BLE_TRACE direction=" +
|
|
7352
|
+
direction +
|
|
7353
|
+
" proto=v" +
|
|
7354
|
+
protocolVersion +
|
|
7355
|
+
" payload=" +
|
|
7356
|
+
payloadBytes +
|
|
7357
|
+
" wire=" +
|
|
7358
|
+
wireBytes +
|
|
7359
|
+
" packets=" +
|
|
7360
|
+
packetCount
|
|
7361
|
+
)
|
|
7362
|
+
}
|
|
7363
|
+
|
|
7042
7364
|
/**
|
|
7043
7365
|
* Send data directly to the glasses using the K900 protocol utility. This method uses
|
|
7044
7366
|
* K900ProtocolUtils.packJsonToK900 to handle C-wrapping and protocol formatting. Large messages
|
|
@@ -7052,8 +7374,19 @@ class MentraLive : SGCManager() {
|
|
|
7052
7374
|
return
|
|
7053
7375
|
}
|
|
7054
7376
|
|
|
7377
|
+
val wireData =
|
|
7378
|
+
try {
|
|
7379
|
+
if (useBinaryWireProtocol && buildNumberInt >= 5) {
|
|
7380
|
+
compactWireJson(JSONObject(data)).toString()
|
|
7381
|
+
} else {
|
|
7382
|
+
data
|
|
7383
|
+
}
|
|
7384
|
+
} catch (_: JSONException) {
|
|
7385
|
+
data
|
|
7386
|
+
}
|
|
7387
|
+
|
|
7055
7388
|
try {
|
|
7056
|
-
val outgoingSummary = summarizeOutgoingMessage(
|
|
7389
|
+
val outgoingSummary = summarizeOutgoingMessage(wireData)
|
|
7057
7390
|
val commandTraceInfo = parseOutgoingBleCommandTraceInfo(data)
|
|
7058
7391
|
val isPhotoRequest = outgoingSummary.contains("type=take_photo")
|
|
7059
7392
|
if (isPhotoRequest) {
|
|
@@ -7065,10 +7398,15 @@ class MentraLive : SGCManager() {
|
|
|
7065
7398
|
)
|
|
7066
7399
|
}
|
|
7067
7400
|
|
|
7401
|
+
if (useBinaryWireProtocol && buildNumberInt >= 5) {
|
|
7402
|
+
sendDataToGlassesBinary(wireData, wakeup, commandTraceInfo, isPhotoRequest)
|
|
7403
|
+
return
|
|
7404
|
+
}
|
|
7405
|
+
|
|
7068
7406
|
// First check if the message needs chunking
|
|
7069
7407
|
// Create a test C-wrapped version to check size
|
|
7070
7408
|
val testWrapper = JSONObject()
|
|
7071
|
-
testWrapper.put("C",
|
|
7409
|
+
testWrapper.put("C", wireData)
|
|
7072
7410
|
if (wakeup) {
|
|
7073
7411
|
testWrapper.put("W", 1)
|
|
7074
7412
|
}
|
|
@@ -7086,14 +7424,14 @@ class MentraLive : SGCManager() {
|
|
|
7086
7424
|
// Extract message ID if present for ACK tracking
|
|
7087
7425
|
var messageId = -1L
|
|
7088
7426
|
try {
|
|
7089
|
-
val originalJson = JSONObject(
|
|
7427
|
+
val originalJson = JSONObject(wireData)
|
|
7090
7428
|
messageId = originalJson.optLong("mId", -1)
|
|
7091
7429
|
} catch (e: JSONException) {
|
|
7092
7430
|
// Not a JSON message or no mId, that's okay
|
|
7093
7431
|
}
|
|
7094
7432
|
|
|
7095
7433
|
// Create chunks
|
|
7096
|
-
val chunks = MessageChunker.createChunks(
|
|
7434
|
+
val chunks = MessageChunker.createChunks(wireData, messageId, wakeup)
|
|
7097
7435
|
Bridge.log("LIVE: Sending " + chunks.size + " chunks")
|
|
7098
7436
|
if (isPhotoRequest) {
|
|
7099
7437
|
Bridge.log(
|
|
@@ -7108,11 +7446,12 @@ class MentraLive : SGCManager() {
|
|
|
7108
7446
|
val chunk = chunks[i]
|
|
7109
7447
|
val chunkStr = chunk.toString()
|
|
7110
7448
|
|
|
7111
|
-
// Pack each chunk using the normal K900 protocol
|
|
7449
|
+
// Pack each chunk using the normal K900 protocol with the negotiated endianness
|
|
7112
7450
|
val packedData =
|
|
7113
7451
|
K900ProtocolUtils.packJsonToK900(
|
|
7114
7452
|
chunkStr,
|
|
7115
|
-
wakeup && i == 0
|
|
7453
|
+
wakeup && i == 0,
|
|
7454
|
+
k900LengthEndian()
|
|
7116
7455
|
) // Only wakeup on first chunk
|
|
7117
7456
|
|
|
7118
7457
|
val trace =
|
|
@@ -7134,7 +7473,7 @@ class MentraLive : SGCManager() {
|
|
|
7134
7473
|
mapOf(
|
|
7135
7474
|
"chunkJsonBytes" to
|
|
7136
7475
|
chunkStr.toByteArray(StandardCharsets.UTF_8).size,
|
|
7137
|
-
"messageBytes" to
|
|
7476
|
+
"messageBytes" to wireData.toByteArray(StandardCharsets.UTF_8).size
|
|
7138
7477
|
)
|
|
7139
7478
|
)
|
|
7140
7479
|
|
|
@@ -7157,17 +7496,18 @@ class MentraLive : SGCManager() {
|
|
|
7157
7496
|
}
|
|
7158
7497
|
} else {
|
|
7159
7498
|
// Normal single message transmission
|
|
7160
|
-
Bridge.log("LIVE: Sending data to glasses: " +
|
|
7499
|
+
Bridge.log("LIVE: Sending data to glasses: " + wireData)
|
|
7161
7500
|
|
|
7162
|
-
// Pack the data using the centralized utility
|
|
7163
|
-
val packedData =
|
|
7501
|
+
// Pack the data using the centralized utility with the negotiated endianness
|
|
7502
|
+
val packedData =
|
|
7503
|
+
K900ProtocolUtils.packJsonToK900(wireData, wakeup, k900LengthEndian())
|
|
7164
7504
|
val trace =
|
|
7165
7505
|
createBleWriteTrace(
|
|
7166
7506
|
commandTraceInfo,
|
|
7167
7507
|
null,
|
|
7168
7508
|
null,
|
|
7169
7509
|
null,
|
|
7170
|
-
|
|
7510
|
+
wireData.toByteArray(StandardCharsets.UTF_8).size,
|
|
7171
7511
|
packedData.size,
|
|
7172
7512
|
wakeup,
|
|
7173
7513
|
false
|
|
@@ -7175,7 +7515,7 @@ class MentraLive : SGCManager() {
|
|
|
7175
7515
|
logBleChunkTrace(
|
|
7176
7516
|
"created",
|
|
7177
7517
|
trace,
|
|
7178
|
-
mapOf("messageBytes" to
|
|
7518
|
+
mapOf("messageBytes" to wireData.toByteArray(StandardCharsets.UTF_8).size)
|
|
7179
7519
|
)
|
|
7180
7520
|
|
|
7181
7521
|
// Queue the data for sending
|
|
@@ -7193,6 +7533,91 @@ class MentraLive : SGCManager() {
|
|
|
7193
7533
|
}
|
|
7194
7534
|
}
|
|
7195
7535
|
|
|
7536
|
+
private fun sendDataToGlassesBinary(
|
|
7537
|
+
data: String,
|
|
7538
|
+
wakeup: Boolean,
|
|
7539
|
+
commandTraceInfo: OutgoingBleCommandTraceInfo,
|
|
7540
|
+
isPhotoRequest: Boolean
|
|
7541
|
+
) {
|
|
7542
|
+
val payloadBytes = data.toByteArray(StandardCharsets.UTF_8)
|
|
7543
|
+
var messageId = 0
|
|
7544
|
+
var ackRequested = false
|
|
7545
|
+
try {
|
|
7546
|
+
val originalJson = JSONObject(data)
|
|
7547
|
+
messageId = (originalJson.optLong("mId", 0L) and 0xFFFFL).toInt()
|
|
7548
|
+
ackRequested = originalJson.has("mId")
|
|
7549
|
+
} catch (_: JSONException) {
|
|
7550
|
+
// Raw non-JSON payloads are still sent as a single binary fragment.
|
|
7551
|
+
}
|
|
7552
|
+
|
|
7553
|
+
val fragments =
|
|
7554
|
+
MessageChunker.createBinaryFragments(
|
|
7555
|
+
payloadBytes,
|
|
7556
|
+
messageId,
|
|
7557
|
+
wakeup,
|
|
7558
|
+
ackRequested
|
|
7559
|
+
)
|
|
7560
|
+
var totalWireBytes = 0
|
|
7561
|
+
for (i in fragments.indices) {
|
|
7562
|
+
val fragment = fragments[i]
|
|
7563
|
+
val packedData =
|
|
7564
|
+
K900ProtocolUtils.packBinaryFragment(
|
|
7565
|
+
fragment.flags,
|
|
7566
|
+
fragment.msgId,
|
|
7567
|
+
fragment.fragIdx,
|
|
7568
|
+
fragment.fragCount,
|
|
7569
|
+
fragment.payload
|
|
7570
|
+
)
|
|
7571
|
+
totalWireBytes += packedData.size
|
|
7572
|
+
|
|
7573
|
+
val trace =
|
|
7574
|
+
createBleWriteTrace(
|
|
7575
|
+
commandTraceInfo,
|
|
7576
|
+
null,
|
|
7577
|
+
fragment.fragIdx,
|
|
7578
|
+
fragment.fragCount,
|
|
7579
|
+
fragment.payload.size,
|
|
7580
|
+
packedData.size,
|
|
7581
|
+
wakeup && i == 0,
|
|
7582
|
+
fragments.size > 1
|
|
7583
|
+
)
|
|
7584
|
+
logBleChunkTrace(
|
|
7585
|
+
"created",
|
|
7586
|
+
trace,
|
|
7587
|
+
mapOf(
|
|
7588
|
+
"messageBytes" to payloadBytes.size,
|
|
7589
|
+
"wireProtocol" to "v2"
|
|
7590
|
+
)
|
|
7591
|
+
)
|
|
7592
|
+
queueData(packedData, trace)
|
|
7593
|
+
|
|
7594
|
+
if (i < fragments.size - 1) {
|
|
7595
|
+
try {
|
|
7596
|
+
Thread.sleep(50)
|
|
7597
|
+
} catch (e: InterruptedException) {
|
|
7598
|
+
Log.w(TAG, "Interrupted during binary fragment delay")
|
|
7599
|
+
}
|
|
7600
|
+
}
|
|
7601
|
+
}
|
|
7602
|
+
|
|
7603
|
+
logWireMetrics(
|
|
7604
|
+
payloadBytes.size,
|
|
7605
|
+
totalWireBytes,
|
|
7606
|
+
fragments.size,
|
|
7607
|
+
BleWireProtocol.PROTOCOL_V2,
|
|
7608
|
+
"phone_to_glasses"
|
|
7609
|
+
)
|
|
7610
|
+
|
|
7611
|
+
if (isPhotoRequest) {
|
|
7612
|
+
Bridge.log(
|
|
7613
|
+
"LIVE: PHOTO PIPELINE BLE handoff — binary v2 queued " +
|
|
7614
|
+
fragments.size +
|
|
7615
|
+
" fragments, wireBytes=" +
|
|
7616
|
+
totalWireBytes
|
|
7617
|
+
)
|
|
7618
|
+
}
|
|
7619
|
+
}
|
|
7620
|
+
|
|
7196
7621
|
private fun parseOutgoingBleCommandTraceInfo(payload: String): OutgoingBleCommandTraceInfo {
|
|
7197
7622
|
return try {
|
|
7198
7623
|
val obj = JSONObject(payload)
|
|
@@ -8400,7 +8825,8 @@ class MentraLive : SGCManager() {
|
|
|
8400
8825
|
val packedData =
|
|
8401
8826
|
K900ProtocolUtils.packDataToK900(
|
|
8402
8827
|
cmdObject.toString().toByteArray(StandardCharsets.UTF_8),
|
|
8403
|
-
K900ProtocolUtils.CMD_TYPE_STRING
|
|
8828
|
+
K900ProtocolUtils.CMD_TYPE_STRING,
|
|
8829
|
+
k900LengthEndian()
|
|
8404
8830
|
)
|
|
8405
8831
|
if (packedData == null) {
|
|
8406
8832
|
Bridge.log("LIVE: Failed to pack Voice Activity Detection setting command")
|