@mentra/bluetooth-sdk 3.2.0-dev.195 → 3.2.0-dev.203
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 +64 -45
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +5 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/Bridge.kt +77 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +12 -6
- package/android/src/main/java/com/mentra/bluetoothsdk/GeneratedChangelogCatalog.kt +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/GeneratedReleaseMetadata.kt +5 -5
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +318 -36
- package/android/src/main/java/com/mentra/bluetoothsdk/MessageAckPolicy.kt +4 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/VersionInfoResponseAccumulator.kt +88 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/WifiRequestResolution.kt +246 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/services/Foreground.kt +50 -16
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/ClassicAudioConnectionTracker.kt +104 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.kt +626 -120
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLiveGattLifecycle.kt +64 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/SGCManager.kt +2 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/SerializedGattCallback.kt +76 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/status/DeviceStatus.kt +16 -3
- package/android/src/test/java/com/mentra/bluetoothsdk/GlassesStatusClassicAudioTest.kt +14 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/MessageAckPolicyTest.kt +16 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/VersionInfoBridgeTest.kt +33 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/VersionInfoResponseAccumulatorTest.kt +169 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/WifiRequestResolutionTest.kt +271 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/services/ForegroundServiceManifestTest.kt +57 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/services/ForegroundServiceTypeTest.kt +52 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/ClassicAudioConnectionTrackerTest.kt +131 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/MentraLiveGattCallbackTest.kt +94 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/MentraLiveGattLifecycleTest.kt +112 -0
- package/build/BluetoothSdk.types.d.ts +62 -1
- package/build/BluetoothSdk.types.d.ts.map +1 -1
- package/build/BluetoothSdk.types.js.map +1 -1
- package/build/_private/BluetoothSdkModule.d.ts +3 -2
- package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
- package/build/_private/BluetoothSdkModule.js.map +1 -1
- package/build/generated/changelogCatalog.d.ts +1 -1
- package/build/generated/changelogCatalog.js +1 -1
- package/build/generated/changelogCatalog.js.map +1 -1
- package/build/generated/releaseMetadata.js +5 -5
- package/build/generated/releaseMetadata.js.map +1 -1
- package/build/index.d.ts +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +3 -0
- package/build/index.js.map +1 -1
- package/ios/BluetoothSdkModule.swift +8 -2
- package/ios/Source/BluetoothSdkDefaults.swift +1 -1
- package/ios/Source/Bridge.swift +78 -1
- package/ios/Source/DeviceManager.swift +13 -6
- package/ios/Source/GeneratedChangelogCatalog.swift +1 -1
- package/ios/Source/GeneratedReleaseMetadata.swift +5 -5
- package/ios/Source/MentraBluetoothSDK.swift +397 -99
- package/ios/Source/WifiRequestResolution.swift +251 -0
- package/ios/Source/requests/VersionInfoResponseAccumulator.swift +101 -0
- package/ios/Source/sgcs/MentraLive.swift +179 -37
- package/ios/Source/sgcs/SGCManager.swift +13 -1
- package/ios/Source/status/DeviceStatus.swift +30 -3
- package/ios/Tests/VersionInfoBridgeTests.swift +32 -0
- package/ios/Tests/VersionInfoResponseAccumulatorTests.swift +181 -0
- package/ios/Tests/WifiRequestResolutionTests.swift +320 -0
- package/package.json +1 -1
- package/src/BluetoothSdk.types.ts +76 -1
- package/src/_private/BluetoothSdkModule.ts +4 -1
- package/src/generated/changelogCatalog.ts +1 -1
- package/src/generated/releaseMetadata.ts +5 -5
- package/src/index.ts +11 -0
|
@@ -2796,7 +2796,11 @@ class MentraLive: NSObject, SGCManager {
|
|
|
2796
2796
|
// Record the session id BEFORE marking ready: an unsolicited glasses_ready
|
|
2797
2797
|
// already runs this full remote-reset flow, so recording (not re-triggering)
|
|
2798
2798
|
// is correct here; version_info detection covers the restart case.
|
|
2799
|
-
|
|
2799
|
+
glassesSessionId = (json["sid"] as? String).flatMap { $0.isEmpty ? nil : $0 }
|
|
2800
|
+
Bridge.sendTypedMessage(
|
|
2801
|
+
"wifi_protocol_session_ready",
|
|
2802
|
+
body: ["sid": glassesSessionId ?? ""]
|
|
2803
|
+
)
|
|
2800
2804
|
readinessCompletedThisBleSession = true
|
|
2801
2805
|
handleGlassesReady()
|
|
2802
2806
|
|
|
@@ -2834,6 +2838,33 @@ class MentraLive: NSObject, SGCManager {
|
|
|
2834
2838
|
}
|
|
2835
2839
|
updateWifiStatus(connected: connected, ssid: ssid, ip: ip, error: wifiError.isEmpty ? nil : wifiError)
|
|
2836
2840
|
|
|
2841
|
+
case "wifi_forget_result":
|
|
2842
|
+
guard wifiResponseEnvelopeIsValid(json, allowLegacy: true) else { return }
|
|
2843
|
+
Bridge.sendWifiForgetResult(
|
|
2844
|
+
requestId: json["requestId"] as? String ?? "",
|
|
2845
|
+
sid: json["sid"] as? String ?? "",
|
|
2846
|
+
ssid: json["ssid"] as? String ?? "",
|
|
2847
|
+
protocolVersion: (json["protocol_version"] as? NSNumber)?.intValue ?? 0,
|
|
2848
|
+
outcome: json["outcome"] as? String ?? "",
|
|
2849
|
+
legacyDispatched: json["dispatched"] as? Bool,
|
|
2850
|
+
connected: json["connected"] as? Bool,
|
|
2851
|
+
currentSsid: json["current_ssid"] as? String ?? "",
|
|
2852
|
+
localIp: json["local_ip"] as? String ?? "",
|
|
2853
|
+
error: (json["error"] as? String).flatMap { $0.isEmpty ? nil : $0 }
|
|
2854
|
+
)
|
|
2855
|
+
|
|
2856
|
+
case "saved_wifi_networks":
|
|
2857
|
+
guard wifiResponseEnvelopeIsValid(json, allowLegacy: false) else { return }
|
|
2858
|
+
guard let networks = json["networks"] as? [String] else { return }
|
|
2859
|
+
Bridge.sendSavedWifiNetworks(
|
|
2860
|
+
requestId: json["requestId"] as? String ?? "",
|
|
2861
|
+
sid: json["sid"] as? String ?? "",
|
|
2862
|
+
protocolVersion: (json["protocol_version"] as? NSNumber)?.intValue ?? 0,
|
|
2863
|
+
outcome: json["outcome"] as? String ?? "",
|
|
2864
|
+
networks: networks,
|
|
2865
|
+
error: (json["error"] as? String).flatMap { $0.isEmpty ? nil : $0 }
|
|
2866
|
+
)
|
|
2867
|
+
|
|
2837
2868
|
case "hotspot_status_update":
|
|
2838
2869
|
let enabled = json["hotspot_enabled"] as? Bool ?? false
|
|
2839
2870
|
let ssid = json["hotspot_ssid"] as? String ?? ""
|
|
@@ -2942,7 +2973,7 @@ class MentraLive: NSObject, SGCManager {
|
|
|
2942
2973
|
Bridge.log("LIVE: Received pong response - connection healthy")
|
|
2943
2974
|
|
|
2944
2975
|
case "entering_pairing_mode":
|
|
2945
|
-
let windowMs = max(
|
|
2976
|
+
let windowMs = max(5000, min(180_000, json["window_ms"] as? Int ?? 120_000))
|
|
2946
2977
|
Bridge.log("LIVE: Glasses entering pairing mode — yield \(windowMs)ms (no forget)")
|
|
2947
2978
|
enterPairingYield(windowMs: windowMs)
|
|
2948
2979
|
let body: [String: Any] = [
|
|
@@ -3086,6 +3117,7 @@ class MentraLive: NSObject, SGCManager {
|
|
|
3086
3117
|
|
|
3087
3118
|
// Extract all fields from JSON (except "type")
|
|
3088
3119
|
var fields: [String: Any] = [:]
|
|
3120
|
+
fields["version_info_type"] = type
|
|
3089
3121
|
for (key, value) in json {
|
|
3090
3122
|
if key != "type" {
|
|
3091
3123
|
fields[key] = value
|
|
@@ -3157,7 +3189,7 @@ class MentraLive: NSObject, SGCManager {
|
|
|
3157
3189
|
maybeSendWireHandshake()
|
|
3158
3190
|
handleGlassesSessionId(json)
|
|
3159
3191
|
|
|
3160
|
-
Bridge.sendVersionInfo(fields)
|
|
3192
|
+
Bridge.sendVersionInfo(fields, responseChunk: type)
|
|
3161
3193
|
} else {
|
|
3162
3194
|
Bridge.log("Unhandled message type: \(type)")
|
|
3163
3195
|
}
|
|
@@ -3647,19 +3679,67 @@ class MentraLive: NSObject, SGCManager {
|
|
|
3647
3679
|
}
|
|
3648
3680
|
|
|
3649
3681
|
func forgetWifiNetwork(_ ssid: String) {
|
|
3650
|
-
|
|
3682
|
+
forgetWifiNetwork(ssid, requestId: nil, sid: nil)
|
|
3683
|
+
}
|
|
3651
3684
|
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3685
|
+
@discardableResult func forgetWifiNetwork(_ ssid: String, requestId: String?, sid: String?) -> Bool {
|
|
3686
|
+
guard let peripheral = connectedPeripheral, peripheral.state == .connected, txCharacteristic != nil,
|
|
3687
|
+
(requestId == nil) == (sid == nil)
|
|
3688
|
+
else { return false }
|
|
3689
|
+
if let requestId, let sid, requestId.isEmpty || sid.isEmpty { return false }
|
|
3690
|
+
transportLog("LIVE: 📶 Sending WiFi forget command for SSID: \(ssid)", bridgeLogging: false)
|
|
3691
|
+
|
|
3692
|
+
guard !ssid.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
|
|
3693
|
+
transportLog("LIVE: Cannot forget WiFi network - SSID is empty", bridgeLogging: false)
|
|
3694
|
+
if let requestId {
|
|
3695
|
+
Bridge.sendWifiForgetResult(
|
|
3696
|
+
requestId: requestId,
|
|
3697
|
+
sid: sid ?? "",
|
|
3698
|
+
ssid: ssid,
|
|
3699
|
+
protocolVersion: 1,
|
|
3700
|
+
outcome: "failed",
|
|
3701
|
+
legacyDispatched: nil,
|
|
3702
|
+
connected: nil,
|
|
3703
|
+
currentSsid: "",
|
|
3704
|
+
localIp: "",
|
|
3705
|
+
error: "invalid_ssid"
|
|
3706
|
+
)
|
|
3707
|
+
}
|
|
3708
|
+
return false
|
|
3655
3709
|
}
|
|
3656
3710
|
|
|
3657
|
-
|
|
3711
|
+
var json: [String: Any] = [
|
|
3658
3712
|
"type": "forget_wifi",
|
|
3659
3713
|
"ssid": ssid,
|
|
3660
3714
|
]
|
|
3715
|
+
if let requestId, !requestId.isEmpty {
|
|
3716
|
+
json["requestId"] = requestId
|
|
3717
|
+
}
|
|
3718
|
+
if let sid, !sid.isEmpty {
|
|
3719
|
+
json["sid"] = sid
|
|
3720
|
+
json["protocolVersion"] = 1
|
|
3721
|
+
}
|
|
3661
3722
|
|
|
3662
|
-
|
|
3723
|
+
// The coordinator dispatches while isolated on MainActor. Keep this path native-only so a
|
|
3724
|
+
// public log delegate cannot synchronously reset the session before the payload is queued.
|
|
3725
|
+
return sendJson(json, wakeUp: true, requireAck: true, bridgeLogging: false)
|
|
3726
|
+
}
|
|
3727
|
+
|
|
3728
|
+
@discardableResult func requestSavedWifiNetworks(requestId: String, sid: String) -> Bool {
|
|
3729
|
+
guard let peripheral = connectedPeripheral, peripheral.state == .connected, txCharacteristic != nil,
|
|
3730
|
+
!requestId.isEmpty, !sid.isEmpty
|
|
3731
|
+
else { return false }
|
|
3732
|
+
var command: [String: Any] = ["type": "request_saved_wifi_networks", "requestId": requestId]
|
|
3733
|
+
command["protocolVersion"] = 1
|
|
3734
|
+
if !sid.isEmpty {
|
|
3735
|
+
command["sid"] = sid
|
|
3736
|
+
}
|
|
3737
|
+
return sendJson(
|
|
3738
|
+
command,
|
|
3739
|
+
wakeUp: true,
|
|
3740
|
+
requireAck: true,
|
|
3741
|
+
bridgeLogging: false
|
|
3742
|
+
)
|
|
3663
3743
|
}
|
|
3664
3744
|
|
|
3665
3745
|
func queryGalleryStatus() {
|
|
@@ -4822,20 +4902,31 @@ class MentraLive: NSObject, SGCManager {
|
|
|
4822
4902
|
wireBytes: Int,
|
|
4823
4903
|
packetCount: Int,
|
|
4824
4904
|
protocolVersion: Int,
|
|
4825
|
-
direction: String
|
|
4905
|
+
direction: String,
|
|
4906
|
+
bridgeLogging: Bool = true
|
|
4826
4907
|
) {
|
|
4827
|
-
|
|
4828
|
-
"BLE_TRACE direction=\(direction) proto=v\(protocolVersion) payload=\(payloadBytes) wire=\(wireBytes) packets=\(packetCount)"
|
|
4908
|
+
transportLog(
|
|
4909
|
+
"BLE_TRACE direction=\(direction) proto=v\(protocolVersion) payload=\(payloadBytes) wire=\(wireBytes) packets=\(packetCount)",
|
|
4910
|
+
bridgeLogging: bridgeLogging
|
|
4829
4911
|
)
|
|
4830
4912
|
}
|
|
4831
4913
|
|
|
4832
|
-
private func
|
|
4914
|
+
private func transportLog(_ message: String, bridgeLogging: Bool) {
|
|
4915
|
+
if bridgeLogging {
|
|
4916
|
+
Bridge.log(message)
|
|
4917
|
+
} else {
|
|
4918
|
+
NSLog("%@", message)
|
|
4919
|
+
}
|
|
4920
|
+
}
|
|
4921
|
+
|
|
4922
|
+
@discardableResult private func sendJsonBinary(
|
|
4833
4923
|
jsonString: String,
|
|
4834
4924
|
messageId: Int64,
|
|
4835
4925
|
trackingId: String,
|
|
4836
4926
|
wakeUp: Bool,
|
|
4837
|
-
requireAck: Bool
|
|
4838
|
-
|
|
4927
|
+
requireAck: Bool,
|
|
4928
|
+
bridgeLogging: Bool
|
|
4929
|
+
) -> Bool {
|
|
4839
4930
|
let payload = Data(jsonString.utf8)
|
|
4840
4931
|
let msgId = UInt16(truncatingIfNeeded: messageId)
|
|
4841
4932
|
let fragments = MessageChunker.createBinaryFragments(
|
|
@@ -4845,8 +4936,8 @@ class MentraLive: NSObject, SGCManager {
|
|
|
4845
4936
|
ackRequested: requireAck && messageId >= 0
|
|
4846
4937
|
)
|
|
4847
4938
|
guard !fragments.isEmpty else {
|
|
4848
|
-
|
|
4849
|
-
return
|
|
4939
|
+
transportLog("LIVE: Failed to create binary wire fragments", bridgeLogging: bridgeLogging)
|
|
4940
|
+
return false
|
|
4850
4941
|
}
|
|
4851
4942
|
|
|
4852
4943
|
var totalWireBytes = 0
|
|
@@ -4858,7 +4949,7 @@ class MentraLive: NSObject, SGCManager {
|
|
|
4858
4949
|
fragCount: fragment.fragCount,
|
|
4859
4950
|
payload: fragment.payload
|
|
4860
4951
|
) else {
|
|
4861
|
-
|
|
4952
|
+
return false
|
|
4862
4953
|
}
|
|
4863
4954
|
totalWireBytes += packed.count
|
|
4864
4955
|
let isFinalChunk = index == fragments.count - 1
|
|
@@ -4875,12 +4966,30 @@ class MentraLive: NSObject, SGCManager {
|
|
|
4875
4966
|
wireBytes: totalWireBytes,
|
|
4876
4967
|
packetCount: fragments.count,
|
|
4877
4968
|
protocolVersion: BleWireProtocol.protocolV2,
|
|
4878
|
-
direction: "phone_to_glasses"
|
|
4969
|
+
direction: "phone_to_glasses",
|
|
4970
|
+
bridgeLogging: bridgeLogging
|
|
4971
|
+
)
|
|
4972
|
+
transportLog(
|
|
4973
|
+
"LIVE: Binary v2 queued \(fragments.count) fragments, wireBytes=\(totalWireBytes)",
|
|
4974
|
+
bridgeLogging: bridgeLogging
|
|
4879
4975
|
)
|
|
4880
|
-
|
|
4976
|
+
return true
|
|
4977
|
+
}
|
|
4978
|
+
|
|
4979
|
+
func sendJson(
|
|
4980
|
+
_ jsonOriginal: [String: Any],
|
|
4981
|
+
wakeUp: Bool = false,
|
|
4982
|
+
requireAck: Bool = true
|
|
4983
|
+
) {
|
|
4984
|
+
sendJson(jsonOriginal, wakeUp: wakeUp, requireAck: requireAck, bridgeLogging: true)
|
|
4881
4985
|
}
|
|
4882
4986
|
|
|
4883
|
-
func sendJson(
|
|
4987
|
+
@discardableResult private func sendJson(
|
|
4988
|
+
_ jsonOriginal: [String: Any],
|
|
4989
|
+
wakeUp: Bool,
|
|
4990
|
+
requireAck: Bool,
|
|
4991
|
+
bridgeLogging: Bool
|
|
4992
|
+
) -> Bool {
|
|
4884
4993
|
do {
|
|
4885
4994
|
var json = jsonOriginal
|
|
4886
4995
|
var messageId: Int64 = -1
|
|
@@ -4904,14 +5013,14 @@ class MentraLive: NSObject, SGCManager {
|
|
|
4904
5013
|
)
|
|
4905
5014
|
|
|
4906
5015
|
if useBinaryWireProtocol, isNewVersion {
|
|
4907
|
-
sendJsonBinary(
|
|
5016
|
+
return sendJsonBinary(
|
|
4908
5017
|
jsonString: jsonString,
|
|
4909
5018
|
messageId: messageId,
|
|
4910
5019
|
trackingId: trackingId,
|
|
4911
5020
|
wakeUp: wakeUp,
|
|
4912
|
-
requireAck: requireAck
|
|
5021
|
+
requireAck: requireAck,
|
|
5022
|
+
bridgeLogging: bridgeLogging
|
|
4913
5023
|
)
|
|
4914
|
-
return
|
|
4915
5024
|
}
|
|
4916
5025
|
|
|
4917
5026
|
// First check if the message needs chunking
|
|
@@ -4925,17 +5034,20 @@ class MentraLive: NSObject, SGCManager {
|
|
|
4925
5034
|
|
|
4926
5035
|
// Check if chunking is needed
|
|
4927
5036
|
if MessageChunker.needsChunking(testWrappedJson) {
|
|
4928
|
-
|
|
5037
|
+
transportLog("LIVE: Message exceeds threshold, chunking required", bridgeLogging: bridgeLogging)
|
|
4929
5038
|
|
|
4930
5039
|
// Create chunks
|
|
4931
5040
|
let chunks = MessageChunker.createChunks(
|
|
4932
5041
|
originalJson: jsonString, messageId: messageId, wakeUp: wakeUp
|
|
4933
5042
|
)
|
|
4934
5043
|
guard !chunks.isEmpty else {
|
|
4935
|
-
|
|
4936
|
-
|
|
5044
|
+
transportLog(
|
|
5045
|
+
"LIVE: Failed to create BLE chunks within K900 packet limit",
|
|
5046
|
+
bridgeLogging: bridgeLogging
|
|
5047
|
+
)
|
|
5048
|
+
return false
|
|
4937
5049
|
}
|
|
4938
|
-
|
|
5050
|
+
transportLog("LIVE: Sending \(chunks.count) chunks", bridgeLogging: bridgeLogging)
|
|
4939
5051
|
|
|
4940
5052
|
// Send each chunk
|
|
4941
5053
|
for (index, chunk) in chunks.enumerated() {
|
|
@@ -4943,7 +5055,11 @@ class MentraLive: NSObject, SGCManager {
|
|
|
4943
5055
|
if let chunkStr = String(data: chunkData, encoding: .utf8) {
|
|
4944
5056
|
// Pack each chunk using the normal K900 protocol
|
|
4945
5057
|
let packedData =
|
|
4946
|
-
packJson(
|
|
5058
|
+
packJson(
|
|
5059
|
+
chunkStr,
|
|
5060
|
+
wakeUp: wakeUp && index == 0,
|
|
5061
|
+
bridgeLogging: bridgeLogging
|
|
5062
|
+
) ?? Data() // Only wakeup on first chunk
|
|
4947
5063
|
|
|
4948
5064
|
// Queue the chunk for sending
|
|
4949
5065
|
// Only track ACK for the final chunk (which has the mId)
|
|
@@ -4964,6 +5080,7 @@ class MentraLive: NSObject, SGCManager {
|
|
|
4964
5080
|
"chunkJsonBytes": chunkStr.data(using: .utf8)?.count,
|
|
4965
5081
|
"messageBytes": jsonData.count,
|
|
4966
5082
|
])
|
|
5083
|
+
guard !packedData.isEmpty else { return false }
|
|
4967
5084
|
queueSend(packedData, id: chunkTrackingId, trace: trace)
|
|
4968
5085
|
|
|
4969
5086
|
// Add small delay between chunks to avoid overwhelming the connection
|
|
@@ -4973,14 +5090,22 @@ class MentraLive: NSObject, SGCManager {
|
|
|
4973
5090
|
}
|
|
4974
5091
|
}
|
|
4975
5092
|
|
|
4976
|
-
|
|
5093
|
+
transportLog("LIVE: All chunks queued for transmission", bridgeLogging: bridgeLogging)
|
|
4977
5094
|
} else {
|
|
4978
5095
|
// Normal single message transmission
|
|
4979
5096
|
if (json["type"] as? String) == "take_photo" {
|
|
4980
|
-
|
|
5097
|
+
transportLog(
|
|
5098
|
+
"LIVE: PHOTO PIPELINE BLE handoff — sendJson -> queueSend take_photo",
|
|
5099
|
+
bridgeLogging: bridgeLogging
|
|
5100
|
+
)
|
|
4981
5101
|
}
|
|
4982
|
-
|
|
4983
|
-
let packedData =
|
|
5102
|
+
transportLog("LIVE: Sending data to glasses: \(jsonString)", bridgeLogging: bridgeLogging)
|
|
5103
|
+
let packedData =
|
|
5104
|
+
packJson(
|
|
5105
|
+
jsonString,
|
|
5106
|
+
wakeUp: wakeUp,
|
|
5107
|
+
bridgeLogging: bridgeLogging
|
|
5108
|
+
) ?? Data()
|
|
4984
5109
|
let trace = createBleWriteTrace(
|
|
4985
5110
|
commandInfo: commandInfo,
|
|
4986
5111
|
chunkId: nil,
|
|
@@ -4994,12 +5119,15 @@ class MentraLive: NSObject, SGCManager {
|
|
|
4994
5119
|
logBleChunkTrace("created", trace, extra: [
|
|
4995
5120
|
"messageBytes": jsonData.count,
|
|
4996
5121
|
])
|
|
5122
|
+
guard !packedData.isEmpty else { return false }
|
|
4997
5123
|
queueSend(packedData, id: trackingId, trace: trace)
|
|
4998
5124
|
}
|
|
5125
|
+
return true
|
|
4999
5126
|
}
|
|
5000
5127
|
} catch {
|
|
5001
|
-
|
|
5128
|
+
transportLog("LIVE: Error creating JSON: \(error)", bridgeLogging: bridgeLogging)
|
|
5002
5129
|
}
|
|
5130
|
+
return false
|
|
5003
5131
|
}
|
|
5004
5132
|
|
|
5005
5133
|
// MARK: - Status Requests
|
|
@@ -5026,7 +5154,14 @@ class MentraLive: NSObject, SGCManager {
|
|
|
5026
5154
|
}
|
|
5027
5155
|
|
|
5028
5156
|
func requestVersionInfo() {
|
|
5029
|
-
|
|
5157
|
+
requestVersionInfo(requestId: nil)
|
|
5158
|
+
}
|
|
5159
|
+
|
|
5160
|
+
func requestVersionInfo(requestId: String?) {
|
|
5161
|
+
var json: [String: Any] = ["type": "request_version"]
|
|
5162
|
+
if let requestId {
|
|
5163
|
+
json["request_id"] = requestId
|
|
5164
|
+
}
|
|
5030
5165
|
sendJson(json)
|
|
5031
5166
|
}
|
|
5032
5167
|
|
|
@@ -5983,7 +6118,11 @@ extension MentraLive {
|
|
|
5983
6118
|
* 1. Wrap with C-field: {"C": jsonData}
|
|
5984
6119
|
* 2. Then pack with BES2700 protocol using little-endian: ## + type + length + {"C": jsonData} + $$
|
|
5985
6120
|
*/
|
|
5986
|
-
private func packJson(
|
|
6121
|
+
private func packJson(
|
|
6122
|
+
_ jsonData: String?,
|
|
6123
|
+
wakeUp: Bool = false,
|
|
6124
|
+
bridgeLogging: Bool = true
|
|
6125
|
+
) -> Data? {
|
|
5987
6126
|
guard let jsonData else { return nil }
|
|
5988
6127
|
|
|
5989
6128
|
do {
|
|
@@ -6006,7 +6145,10 @@ extension MentraLive {
|
|
|
6006
6145
|
)
|
|
6007
6146
|
|
|
6008
6147
|
} catch {
|
|
6009
|
-
|
|
6148
|
+
transportLog(
|
|
6149
|
+
"Error creating JSON wrapper for K900: \(error)",
|
|
6150
|
+
bridgeLogging: bridgeLogging
|
|
6151
|
+
)
|
|
6010
6152
|
return nil
|
|
6011
6153
|
}
|
|
6012
6154
|
}
|
|
@@ -195,8 +195,10 @@ protocol SGCManager {
|
|
|
195
195
|
// MARK: - Network Management
|
|
196
196
|
|
|
197
197
|
func requestWifiScan(scanId: String?)
|
|
198
|
+
@discardableResult func requestSavedWifiNetworks(requestId: String, sid: String) -> Bool
|
|
198
199
|
func sendWifiCredentials(_ ssid: String, _ password: String)
|
|
199
200
|
func forgetWifiNetwork(_ ssid: String)
|
|
201
|
+
@discardableResult func forgetWifiNetwork(_ ssid: String, requestId: String?, sid: String?) -> Bool
|
|
200
202
|
func sendHotspotState(_ enabled: Bool)
|
|
201
203
|
func sendWifiAdbState(_ enabled: Bool)
|
|
202
204
|
func sendOtaStart(otaVersionUrl: String?)
|
|
@@ -232,7 +234,17 @@ protocol SGCManager {
|
|
|
232
234
|
/// doesn't seem to work for concurrency reasons :(
|
|
233
235
|
/// we can make read-only getters for convienence though:
|
|
234
236
|
extension SGCManager {
|
|
235
|
-
var isMicSuspendedForAudio: Bool {
|
|
237
|
+
var isMicSuspendedForAudio: Bool {
|
|
238
|
+
false
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
@discardableResult func requestSavedWifiNetworks(requestId _: String, sid _: String) -> Bool {
|
|
242
|
+
false
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
@discardableResult func forgetWifiNetwork(_: String, requestId _: String?, sid _: String?) -> Bool {
|
|
246
|
+
false
|
|
247
|
+
}
|
|
236
248
|
|
|
237
249
|
/// Default: no-op. Only G2 renders positioned text containers; other glasses ignore it.
|
|
238
250
|
func sendPositionedText(
|
|
@@ -376,6 +376,10 @@ public struct VersionInfoResult: CustomStringConvertible {
|
|
|
376
376
|
public let appVersion: String
|
|
377
377
|
public let packageName: String
|
|
378
378
|
public let hotspotOtaVersion: Int
|
|
379
|
+
public let wifiForgetResultVersion: Int?
|
|
380
|
+
public let savedWifiNetworksVersion: Int?
|
|
381
|
+
public let versionInfoType: String?
|
|
382
|
+
public let sid: String?
|
|
379
383
|
|
|
380
384
|
init(status: GlassesStatus) {
|
|
381
385
|
androidVersion = status.androidVersion
|
|
@@ -388,6 +392,10 @@ public struct VersionInfoResult: CustomStringConvertible {
|
|
|
388
392
|
appVersion = status.appVersion
|
|
389
393
|
packageName = status.packageName
|
|
390
394
|
hotspotOtaVersion = status.hotspotOtaVersion
|
|
395
|
+
wifiForgetResultVersion = nil
|
|
396
|
+
savedWifiNetworksVersion = nil
|
|
397
|
+
versionInfoType = nil
|
|
398
|
+
sid = nil
|
|
391
399
|
}
|
|
392
400
|
|
|
393
401
|
init(values: [String: Any]) {
|
|
@@ -404,6 +412,14 @@ public struct VersionInfoResult: CustomStringConvertible {
|
|
|
404
412
|
intValue(values["hotspotOtaVersion"])
|
|
405
413
|
?? intValue(values["hotspot_ota_version"])
|
|
406
414
|
?? 0
|
|
415
|
+
wifiForgetResultVersion =
|
|
416
|
+
intValue(values["wifiForgetResultVersion"])
|
|
417
|
+
?? intValue(values["wifi_forget_result_version"])
|
|
418
|
+
savedWifiNetworksVersion =
|
|
419
|
+
intValue(values["savedWifiNetworksVersion"])
|
|
420
|
+
?? intValue(values["saved_wifi_networks_version"])
|
|
421
|
+
versionInfoType = stringValue(values, "versionInfoType", "version_info_type")
|
|
422
|
+
sid = stringValue(values, "sid")
|
|
407
423
|
}
|
|
408
424
|
|
|
409
425
|
public var dictionary: [String: Any] {
|
|
@@ -420,12 +436,23 @@ public struct VersionInfoResult: CustomStringConvertible {
|
|
|
420
436
|
if let systemTimeMs {
|
|
421
437
|
values["systemTimeMs"] = systemTimeMs
|
|
422
438
|
}
|
|
423
|
-
// Only when known
|
|
424
|
-
//
|
|
425
|
-
// known identity with "absent", which the OTA guard reads as stock.
|
|
439
|
+
// Only when known: per-chunk events may omit package identity. Emitting an empty value
|
|
440
|
+
// would overwrite a known identity, which the OTA guard uses to identify stock.
|
|
426
441
|
if !packageName.isEmpty {
|
|
427
442
|
values["packageName"] = packageName
|
|
428
443
|
}
|
|
444
|
+
if let wifiForgetResultVersion {
|
|
445
|
+
values["wifiForgetResultVersion"] = wifiForgetResultVersion
|
|
446
|
+
}
|
|
447
|
+
if let savedWifiNetworksVersion {
|
|
448
|
+
values["savedWifiNetworksVersion"] = savedWifiNetworksVersion
|
|
449
|
+
}
|
|
450
|
+
if let versionInfoType {
|
|
451
|
+
values["versionInfoType"] = versionInfoType
|
|
452
|
+
}
|
|
453
|
+
if let sid {
|
|
454
|
+
values["sid"] = sid
|
|
455
|
+
}
|
|
429
456
|
return values
|
|
430
457
|
}
|
|
431
458
|
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
@testable import MentraBluetoothSDK
|
|
2
|
+
import XCTest
|
|
3
|
+
|
|
4
|
+
final class VersionInfoBridgeTests: XCTestCase {
|
|
5
|
+
func testWireMetadataSurvivesBridgeNormalization() {
|
|
6
|
+
let accumulator = VersionInfoResponseAccumulator(expectedRequestId: "request-1")
|
|
7
|
+
let delivered = expectation(description: "Both version chunks")
|
|
8
|
+
delivered.expectedFulfillmentCount = 2
|
|
9
|
+
var outcomes: [VersionInfoAccumulatorOutcome] = []
|
|
10
|
+
let sink = Bridge.addEventSink { type, body in
|
|
11
|
+
guard type == "version_info" else { return }
|
|
12
|
+
outcomes.append(accumulator.accept(body))
|
|
13
|
+
delivered.fulfill()
|
|
14
|
+
}
|
|
15
|
+
defer { Bridge.removeEventSink(sink) }
|
|
16
|
+
Bridge.sendVersionInfo([
|
|
17
|
+
"request_id": "request-1", "sid": "asg-1", "chunkCount": 2,
|
|
18
|
+
"chunkIndex": 1, "final": false, "build_number": "42",
|
|
19
|
+
], responseChunk: "version_info_1")
|
|
20
|
+
Bridge.sendVersionInfo([
|
|
21
|
+
"request_id": "request-1", "sid": "asg-1", "chunkCount": 2,
|
|
22
|
+
"chunkIndex": 2, "final": true, "bes_fw_version": "new",
|
|
23
|
+
], responseChunk: "version_info_3")
|
|
24
|
+
wait(for: [delivered], timeout: 2)
|
|
25
|
+
guard case .waiting = outcomes[0], case let .complete(result) = outcomes[1] else {
|
|
26
|
+
return XCTFail("Expected an explicitly complete combined response")
|
|
27
|
+
}
|
|
28
|
+
XCTAssertEqual(result.buildNumber, "42")
|
|
29
|
+
XCTAssertEqual(result.besFirmwareVersion, "new")
|
|
30
|
+
XCTAssertFalse(result.dictionary.keys.contains { $0.hasPrefix("_response") })
|
|
31
|
+
}
|
|
32
|
+
}
|