@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
|
@@ -8,9 +8,9 @@ private final class ActiveStreamKeepAlive {
|
|
|
8
8
|
var pendingAckId: String?
|
|
9
9
|
var missedAckCount = 0
|
|
10
10
|
var task: Task<Void, Never>?
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
/// Missed-ACK counting only begins once the stream is confirmed live/coming up, so a slow
|
|
12
|
+
/// startup (glasses can't ACK until they reach starting/streaming) can't trip a false
|
|
13
|
+
/// keep-alive timeout before the stream is ever up.
|
|
14
14
|
var armed = false
|
|
15
15
|
|
|
16
16
|
init(streamId: String, intervalSeconds: Int) {
|
|
@@ -47,8 +47,8 @@ private final class PendingWifiScan {
|
|
|
47
47
|
let pending: PendingResponse<[WifiScanResult]>
|
|
48
48
|
let scanId: String
|
|
49
49
|
var latestResults: [WifiScanResult] = []
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
/// Chunks accumulated from scanId-echoing glasses, deduplicated by SSID;
|
|
51
|
+
/// resolved only when the glasses flag the scan complete.
|
|
52
52
|
var accumulated: [WifiScanResult] = []
|
|
53
53
|
|
|
54
54
|
init(pending: PendingResponse<[WifiScanResult]>, scanId: String) {
|
|
@@ -57,21 +57,68 @@ private final class PendingWifiScan {
|
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
private enum WifiStatusOperation {
|
|
61
|
-
case connect
|
|
62
|
-
case forget
|
|
63
|
-
}
|
|
64
|
-
|
|
65
60
|
@MainActor
|
|
66
61
|
private final class PendingWifiStatusRequest {
|
|
67
|
-
let operation: WifiStatusOperation
|
|
68
62
|
let ssid: String
|
|
69
63
|
let pending: PendingResponse<WifiStatusEvent>
|
|
70
64
|
|
|
71
|
-
init(
|
|
72
|
-
|
|
65
|
+
init(
|
|
66
|
+
ssid: String,
|
|
67
|
+
pending: PendingResponse<WifiStatusEvent>
|
|
68
|
+
) {
|
|
69
|
+
self.ssid = ssid
|
|
70
|
+
self.pending = pending
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
@MainActor
|
|
75
|
+
private final class PendingWifiForgetRequest {
|
|
76
|
+
let ssid: String
|
|
77
|
+
let requestId: String
|
|
78
|
+
var sid: String
|
|
79
|
+
let epoch: UInt64
|
|
80
|
+
let pending: PendingResponse<WifiForgetResult>
|
|
81
|
+
var mode: WifiRequestMode
|
|
82
|
+
var commandSent = false
|
|
83
|
+
|
|
84
|
+
init(
|
|
85
|
+
ssid: String,
|
|
86
|
+
requestId: String,
|
|
87
|
+
sid: String,
|
|
88
|
+
epoch: UInt64,
|
|
89
|
+
pending: PendingResponse<WifiForgetResult>,
|
|
90
|
+
mode: WifiRequestMode
|
|
91
|
+
) {
|
|
73
92
|
self.ssid = ssid
|
|
93
|
+
self.requestId = requestId
|
|
94
|
+
self.sid = sid
|
|
95
|
+
self.epoch = epoch
|
|
96
|
+
self.pending = pending
|
|
97
|
+
self.mode = mode
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
@MainActor
|
|
102
|
+
private final class PendingSavedWifiNetworks {
|
|
103
|
+
let requestId: String
|
|
104
|
+
var sid: String
|
|
105
|
+
let epoch: UInt64
|
|
106
|
+
let pending: PendingResponse<SavedWifiNetworksResult>
|
|
107
|
+
var mode: WifiRequestMode
|
|
108
|
+
var commandSent = false
|
|
109
|
+
|
|
110
|
+
init(
|
|
111
|
+
requestId: String,
|
|
112
|
+
sid: String,
|
|
113
|
+
epoch: UInt64,
|
|
114
|
+
pending: PendingResponse<SavedWifiNetworksResult>,
|
|
115
|
+
mode: WifiRequestMode
|
|
116
|
+
) {
|
|
117
|
+
self.requestId = requestId
|
|
118
|
+
self.sid = sid
|
|
119
|
+
self.epoch = epoch
|
|
74
120
|
self.pending = pending
|
|
121
|
+
self.mode = mode
|
|
75
122
|
}
|
|
76
123
|
}
|
|
77
124
|
|
|
@@ -101,17 +148,28 @@ private final class PendingVideoRecordingRequest {
|
|
|
101
148
|
}
|
|
102
149
|
}
|
|
103
150
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
151
|
+
@MainActor
|
|
152
|
+
private final class PendingVersionInfoRequest {
|
|
153
|
+
let pending: PendingResponse<VersionInfoResult>
|
|
154
|
+
let accumulator: VersionInfoResponseAccumulator
|
|
155
|
+
|
|
156
|
+
init(pending: PendingResponse<VersionInfoResult>, requestId: String) {
|
|
157
|
+
self.pending = pending
|
|
158
|
+
accumulator = VersionInfoResponseAccumulator(expectedRequestId: requestId)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/// seq records send order (assigned and handed to the BLE queue in a single
|
|
163
|
+
/// MainActor turn) so that an id-carrying status for a newer start can
|
|
164
|
+
/// identify which older in-flight starts it preempted.
|
|
107
165
|
@MainActor
|
|
108
166
|
private final class PendingStreamStart {
|
|
109
167
|
let seq: Int
|
|
110
168
|
let pending: PendingResponse<StreamStatusEvent>
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
169
|
+
/// Set when an id-carrying error arrives: a fatal publisher error never
|
|
170
|
+
/// reaches the reconnect machinery and winds down with a streamId-less
|
|
171
|
+
/// stopped, so the stash both attributes that stopped to this start and
|
|
172
|
+
/// preserves the real error details for the rejection.
|
|
115
173
|
var lastError: StreamStatusEvent?
|
|
116
174
|
|
|
117
175
|
init(seq: Int, pending: PendingResponse<StreamStatusEvent>) {
|
|
@@ -121,7 +179,7 @@ private final class PendingStreamStart {
|
|
|
121
179
|
}
|
|
122
180
|
|
|
123
181
|
@MainActor
|
|
124
|
-
|
|
182
|
+
final class PendingResponse<T> {
|
|
125
183
|
private let operation: String
|
|
126
184
|
private var continuation: CheckedContinuation<T, Error>?
|
|
127
185
|
private var timeoutTask: Task<Void, Never>?
|
|
@@ -150,22 +208,39 @@ private final class PendingResponse<T> {
|
|
|
150
208
|
continuation = nil
|
|
151
209
|
}
|
|
152
210
|
|
|
153
|
-
func wait(timeoutMs: Int =
|
|
211
|
+
func wait(timeoutMs: Int? = 15000) async throws -> T {
|
|
212
|
+
if Task.isCancelled {
|
|
213
|
+
throw BluetoothSdkError(code: "request_cancelled", message: "\(operation) was cancelled.")
|
|
214
|
+
}
|
|
154
215
|
if let result {
|
|
155
216
|
return try result.get()
|
|
156
217
|
}
|
|
157
|
-
return try await
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
218
|
+
return try await withTaskCancellationHandler {
|
|
219
|
+
try await withCheckedThrowingContinuation { continuation in
|
|
220
|
+
self.continuation = continuation
|
|
221
|
+
if let timeoutMs {
|
|
222
|
+
timeoutTask = Task { @MainActor [weak self] in
|
|
223
|
+
do {
|
|
224
|
+
try await Task.sleep(nanoseconds: UInt64(timeoutMs) * 1_000_000)
|
|
225
|
+
} catch {
|
|
226
|
+
return
|
|
227
|
+
}
|
|
228
|
+
self?.reject(
|
|
229
|
+
BluetoothSdkError(
|
|
230
|
+
code: "request_timeout",
|
|
231
|
+
message: "\(self?.operation ?? "Request") timed out waiting for glasses response."
|
|
232
|
+
)
|
|
233
|
+
)
|
|
234
|
+
}
|
|
164
235
|
}
|
|
165
|
-
|
|
236
|
+
}
|
|
237
|
+
} onCancel: {
|
|
238
|
+
Task { @MainActor [weak self] in
|
|
239
|
+
guard let self else { return }
|
|
240
|
+
reject(
|
|
166
241
|
BluetoothSdkError(
|
|
167
|
-
code: "
|
|
168
|
-
message: "\(
|
|
242
|
+
code: "request_cancelled",
|
|
243
|
+
message: "\(operation) was cancelled."
|
|
169
244
|
)
|
|
170
245
|
)
|
|
171
246
|
}
|
|
@@ -175,12 +250,12 @@ private final class PendingResponse<T> {
|
|
|
175
250
|
|
|
176
251
|
@MainActor
|
|
177
252
|
public final class MentraBluetoothSDK {
|
|
178
|
-
private static let wifiScanTimeoutMs =
|
|
253
|
+
private static let wifiScanTimeoutMs = 20000
|
|
179
254
|
// A photo response is terminal only after capture, encoding, transport, and upload.
|
|
180
255
|
// Max-quality BLE fallback can legitimately exceed the generic command deadline.
|
|
181
|
-
private static let photoRequestTimeoutMs =
|
|
182
|
-
private static let otaBesVersionWaitMs =
|
|
183
|
-
private static let otaMtkVersionWaitMs =
|
|
256
|
+
private static let photoRequestTimeoutMs = 30000
|
|
257
|
+
private static let otaBesVersionWaitMs = 5000
|
|
258
|
+
private static let otaMtkVersionWaitMs = 2000
|
|
184
259
|
private static let otaVersionPollMs = 100
|
|
185
260
|
private static let defaultStreamKeepAliveIntervalSeconds = 5
|
|
186
261
|
|
|
@@ -216,10 +291,13 @@ public final class MentraBluetoothSDK {
|
|
|
216
291
|
private var pendingOtaQuery: PendingResponse<OtaQueryResult>?
|
|
217
292
|
private var pendingOtaStart: PendingResponse<OtaStartAckEvent>?
|
|
218
293
|
private var pendingWifiScan: PendingWifiScan?
|
|
294
|
+
private var pendingSavedWifiNetworks: PendingSavedWifiNetworks?
|
|
219
295
|
private var wifiScanTask: Task<[WifiScanResult], Error>?
|
|
220
296
|
private var pendingWifiStatus: PendingWifiStatusRequest?
|
|
297
|
+
private var pendingWifiForget: PendingWifiForgetRequest?
|
|
221
298
|
private var pendingHotspotStatus: PendingHotspotStatusRequest?
|
|
222
|
-
private var pendingVersionInfo:
|
|
299
|
+
private var pendingVersionInfo: PendingVersionInfoRequest?
|
|
300
|
+
private let wifiSessionCapabilities = WifiSessionCapabilities()
|
|
223
301
|
private var configuredOtaVersionUrl: String?
|
|
224
302
|
|
|
225
303
|
public init(configuration: MentraBluetoothSDKConfiguration = .default) {
|
|
@@ -609,7 +687,8 @@ public final class MentraBluetoothSDK {
|
|
|
609
687
|
for key in ["button_photo_zsl_mfnr", "button_photo_mfnr", "button_photo_zsl", "button_photo_noise_reduction",
|
|
610
688
|
"button_photo_edge_enhancement", "button_photo_isp_digital_gain",
|
|
611
689
|
"button_photo_isp_analog_gain", "button_photo_ae_exposure_divisor",
|
|
612
|
-
"button_photo_iso_cap", "button_photo_compress", "button_photo_sound"]
|
|
690
|
+
"button_photo_iso_cap", "button_photo_compress", "button_photo_sound"]
|
|
691
|
+
{
|
|
613
692
|
DeviceStore.shared.remove(cat, key)
|
|
614
693
|
}
|
|
615
694
|
}
|
|
@@ -863,7 +942,8 @@ public final class MentraBluetoothSDK {
|
|
|
863
942
|
return try await pending.wait(timeoutMs: MentraBluetoothSDK.wifiScanTimeoutMs)
|
|
864
943
|
} catch {
|
|
865
944
|
if (error as? BluetoothSdkError)?.code == "request_timeout",
|
|
866
|
-
!request.latestResults.isEmpty
|
|
945
|
+
!request.latestResults.isEmpty
|
|
946
|
+
{
|
|
867
947
|
return request.latestResults
|
|
868
948
|
}
|
|
869
949
|
throw error
|
|
@@ -874,14 +954,14 @@ public final class MentraBluetoothSDK {
|
|
|
874
954
|
}
|
|
875
955
|
|
|
876
956
|
public func sendWifiCredentials(ssid: String, password: String) async throws -> WifiStatusEvent {
|
|
877
|
-
guard pendingWifiStatus == nil else {
|
|
957
|
+
guard pendingWifiStatus == nil, pendingWifiForget == nil else {
|
|
878
958
|
throw BluetoothSdkError(
|
|
879
959
|
code: "request_in_flight",
|
|
880
960
|
message: "A WiFi status command is already waiting for a glasses response."
|
|
881
961
|
)
|
|
882
962
|
}
|
|
883
963
|
let pending = PendingResponse<WifiStatusEvent>(operation: "WiFi connect request")
|
|
884
|
-
pendingWifiStatus = PendingWifiStatusRequest(
|
|
964
|
+
pendingWifiStatus = PendingWifiStatusRequest(ssid: ssid, pending: pending)
|
|
885
965
|
DeviceManager.shared.sendWifiCredentials(ssid, password)
|
|
886
966
|
do {
|
|
887
967
|
let event = try await pending.wait()
|
|
@@ -897,25 +977,83 @@ public final class MentraBluetoothSDK {
|
|
|
897
977
|
}
|
|
898
978
|
}
|
|
899
979
|
|
|
900
|
-
public func forgetWifiNetwork(ssid: String) async throws ->
|
|
901
|
-
guard
|
|
980
|
+
public func forgetWifiNetwork(ssid: String) async throws -> WifiForgetResult {
|
|
981
|
+
guard wifiSsidIsValid(ssid) else {
|
|
982
|
+
throw BluetoothSdkError(code: "invalid_ssid", message: "WiFi SSID cannot be empty.")
|
|
983
|
+
}
|
|
984
|
+
guard pendingWifiStatus == nil, pendingWifiForget == nil else {
|
|
902
985
|
throw BluetoothSdkError(
|
|
903
986
|
code: "request_in_flight",
|
|
904
987
|
message: "A WiFi status command is already waiting for a glasses response."
|
|
905
988
|
)
|
|
906
989
|
}
|
|
907
|
-
let pending = PendingResponse<
|
|
908
|
-
|
|
909
|
-
|
|
990
|
+
let pending = PendingResponse<WifiForgetResult>(operation: "WiFi forget request")
|
|
991
|
+
let requestId = "forget-\(UUID().uuidString)"
|
|
992
|
+
let request = PendingWifiForgetRequest(
|
|
993
|
+
ssid: ssid,
|
|
994
|
+
requestId: requestId,
|
|
995
|
+
sid: wifiSessionCapabilities.sessionId,
|
|
996
|
+
epoch: wifiSessionCapabilities.epoch,
|
|
997
|
+
pending: pending,
|
|
998
|
+
mode: wifiSessionCapabilities.forgetMode()
|
|
999
|
+
)
|
|
1000
|
+
pendingWifiForget = request
|
|
1001
|
+
dispatchWifiForgetIfReady(request)
|
|
910
1002
|
do {
|
|
911
1003
|
let event = try await pending.wait()
|
|
912
|
-
if
|
|
913
|
-
|
|
1004
|
+
if pendingWifiForget === request {
|
|
1005
|
+
pendingWifiForget = nil
|
|
914
1006
|
}
|
|
915
1007
|
return event
|
|
916
1008
|
} catch {
|
|
917
|
-
if
|
|
918
|
-
|
|
1009
|
+
if pendingWifiForget === request {
|
|
1010
|
+
pendingWifiForget = nil
|
|
1011
|
+
}
|
|
1012
|
+
if let sdkError = error as? BluetoothSdkError, sdkError.code == "request_timeout", request.mode == .discovering {
|
|
1013
|
+
throw BluetoothSdkError(code: wifiCapabilityNegotiationTimeoutCode, message: "WiFi capability negotiation timed out.")
|
|
1014
|
+
}
|
|
1015
|
+
throw error
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
public func getSavedWifiNetworks() async throws -> SavedWifiNetworksResult {
|
|
1020
|
+
guard pendingSavedWifiNetworks == nil else {
|
|
1021
|
+
throw BluetoothSdkError(
|
|
1022
|
+
code: "request_in_flight",
|
|
1023
|
+
message: "A saved WiFi networks request is already waiting for a glasses response."
|
|
1024
|
+
)
|
|
1025
|
+
}
|
|
1026
|
+
let requestId = "saved-\(UUID().uuidString)"
|
|
1027
|
+
let mode = wifiSessionCapabilities.savedNetworksMode()
|
|
1028
|
+
if mode == .legacy || mode == .unsupported {
|
|
1029
|
+
return SavedWifiNetworksResult(
|
|
1030
|
+
outcome: .unsupported,
|
|
1031
|
+
networks: [],
|
|
1032
|
+
error: "saved_wifi_networks_unsupported"
|
|
1033
|
+
)
|
|
1034
|
+
}
|
|
1035
|
+
let pending = PendingResponse<SavedWifiNetworksResult>(operation: "Saved WiFi networks request")
|
|
1036
|
+
let request = PendingSavedWifiNetworks(
|
|
1037
|
+
requestId: requestId,
|
|
1038
|
+
sid: wifiSessionCapabilities.sessionId,
|
|
1039
|
+
epoch: wifiSessionCapabilities.epoch,
|
|
1040
|
+
pending: pending,
|
|
1041
|
+
mode: mode
|
|
1042
|
+
)
|
|
1043
|
+
pendingSavedWifiNetworks = request
|
|
1044
|
+
dispatchSavedWifiNetworksIfReady(request)
|
|
1045
|
+
do {
|
|
1046
|
+
let networks = try await pending.wait()
|
|
1047
|
+
if pendingSavedWifiNetworks === request {
|
|
1048
|
+
pendingSavedWifiNetworks = nil
|
|
1049
|
+
}
|
|
1050
|
+
return networks
|
|
1051
|
+
} catch {
|
|
1052
|
+
if pendingSavedWifiNetworks === request {
|
|
1053
|
+
pendingSavedWifiNetworks = nil
|
|
1054
|
+
}
|
|
1055
|
+
if let sdkError = error as? BluetoothSdkError, sdkError.code == "request_timeout", request.mode == .discovering {
|
|
1056
|
+
throw BluetoothSdkError(code: wifiCapabilityNegotiationTimeoutCode, message: "WiFi capability negotiation timed out.")
|
|
919
1057
|
}
|
|
920
1058
|
throw error
|
|
921
1059
|
}
|
|
@@ -1064,7 +1202,7 @@ public final class MentraBluetoothSDK {
|
|
|
1064
1202
|
stopStreamKeepAliveMonitor()
|
|
1065
1203
|
DeviceManager.shared.startStream(values)
|
|
1066
1204
|
do {
|
|
1067
|
-
let event = try await pending.wait(timeoutMs:
|
|
1205
|
+
let event = try await pending.wait(timeoutMs: 30000)
|
|
1068
1206
|
pendingStreamStarts.removeValue(forKey: streamId)
|
|
1069
1207
|
if startSdkKeepAlive {
|
|
1070
1208
|
startStreamKeepAliveMonitor(
|
|
@@ -1123,7 +1261,7 @@ public final class MentraBluetoothSDK {
|
|
|
1123
1261
|
stopStreamKeepAliveMonitor()
|
|
1124
1262
|
DeviceManager.shared.stopStream()
|
|
1125
1263
|
do {
|
|
1126
|
-
let event = try await pending.wait(timeoutMs:
|
|
1264
|
+
let event = try await pending.wait(timeoutMs: 15000)
|
|
1127
1265
|
if pendingStreamStop?.pending === pending {
|
|
1128
1266
|
pendingStreamStop = nil
|
|
1129
1267
|
}
|
|
@@ -1195,7 +1333,7 @@ public final class MentraBluetoothSDK {
|
|
|
1195
1333
|
)
|
|
1196
1334
|
DeviceManager.shared.stopVideoRecording(requestId, webhookUrl, authToken)
|
|
1197
1335
|
do {
|
|
1198
|
-
let timeoutMs = waitForUpload ? videoUploadStopTimeoutMs :
|
|
1336
|
+
let timeoutMs = waitForUpload ? videoUploadStopTimeoutMs : 15000
|
|
1199
1337
|
let event = try await pending.wait(timeoutMs: timeoutMs)
|
|
1200
1338
|
pendingVideoRecordingRequests.removeValue(forKey: requestId)
|
|
1201
1339
|
return event
|
|
@@ -1242,17 +1380,19 @@ public final class MentraBluetoothSDK {
|
|
|
1242
1380
|
message: "A version info request is already waiting for a glasses response."
|
|
1243
1381
|
)
|
|
1244
1382
|
}
|
|
1383
|
+
let requestId = UUID().uuidString
|
|
1245
1384
|
let pending = PendingResponse<VersionInfoResult>(operation: "version info request")
|
|
1246
|
-
|
|
1247
|
-
|
|
1385
|
+
let request = PendingVersionInfoRequest(pending: pending, requestId: requestId)
|
|
1386
|
+
pendingVersionInfo = request
|
|
1387
|
+
DeviceManager.shared.requestVersionInfo(requestId: requestId)
|
|
1248
1388
|
do {
|
|
1249
1389
|
let status = try await pending.wait()
|
|
1250
|
-
if pendingVersionInfo ===
|
|
1390
|
+
if pendingVersionInfo === request {
|
|
1251
1391
|
pendingVersionInfo = nil
|
|
1252
1392
|
}
|
|
1253
1393
|
return status
|
|
1254
1394
|
} catch {
|
|
1255
|
-
if pendingVersionInfo ===
|
|
1395
|
+
if pendingVersionInfo === request {
|
|
1256
1396
|
pendingVersionInfo = nil
|
|
1257
1397
|
}
|
|
1258
1398
|
throw error
|
|
@@ -1384,7 +1524,9 @@ public final class MentraBluetoothSDK {
|
|
|
1384
1524
|
try await startOtaCommand(otaVersionUrl: otaVersionUrl)
|
|
1385
1525
|
}
|
|
1386
1526
|
|
|
1387
|
-
func sendOtaQueryStatus() async throws -> OtaQueryResult {
|
|
1527
|
+
func sendOtaQueryStatus() async throws -> OtaQueryResult {
|
|
1528
|
+
try await queryOtaStatus()
|
|
1529
|
+
}
|
|
1388
1530
|
|
|
1389
1531
|
func startAr99OtaFromFile(_ path: String) throws -> Bool {
|
|
1390
1532
|
try requireGlassesConnected(operation: "start AR99 OTA")
|
|
@@ -1448,7 +1590,7 @@ public final class MentraBluetoothSDK {
|
|
|
1448
1590
|
timeoutMs: Int,
|
|
1449
1591
|
isReady: (GlassesStatus) -> Bool
|
|
1450
1592
|
) async -> GlassesStatus {
|
|
1451
|
-
let deadline = Date().addingTimeInterval(Double(timeoutMs) /
|
|
1593
|
+
let deadline = Date().addingTimeInterval(Double(timeoutMs) / 1000)
|
|
1452
1594
|
var status = initialStatus
|
|
1453
1595
|
while Date() < deadline {
|
|
1454
1596
|
status = glassesStatus
|
|
@@ -1456,7 +1598,7 @@ public final class MentraBluetoothSDK {
|
|
|
1456
1598
|
return status
|
|
1457
1599
|
}
|
|
1458
1600
|
|
|
1459
|
-
let remainingMs = max(0, Int(deadline.timeIntervalSinceNow *
|
|
1601
|
+
let remainingMs = max(0, Int(deadline.timeIntervalSinceNow * 1000))
|
|
1460
1602
|
let sleepMs = min(Self.otaVersionPollMs, remainingMs)
|
|
1461
1603
|
if sleepMs <= 0 {
|
|
1462
1604
|
break
|
|
@@ -1498,6 +1640,7 @@ public final class MentraBluetoothSDK {
|
|
|
1498
1640
|
|
|
1499
1641
|
public func invalidate() {
|
|
1500
1642
|
stopStreamKeepAliveMonitor()
|
|
1643
|
+
resetWifiProtocolSession(sessionId: "", code: "sdk_closed")
|
|
1501
1644
|
if let bluetoothAvailabilityListenerId {
|
|
1502
1645
|
BluetoothAvailability.shared.removeStateListener(bluetoothAvailabilityListenerId)
|
|
1503
1646
|
self.bluetoothAvailabilityListenerId = nil
|
|
@@ -1743,13 +1886,13 @@ public final class MentraBluetoothSDK {
|
|
|
1743
1886
|
}
|
|
1744
1887
|
}
|
|
1745
1888
|
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1889
|
+
/// The glasses process BLE commands FIFO and every start_stream begins by
|
|
1890
|
+
/// stopping whatever runs, so an id-carrying status proving start X's
|
|
1891
|
+
/// start path ran on the glasses also proves every lower-seq start has
|
|
1892
|
+
/// already been preempted. Their only verdict on current firmware is a
|
|
1893
|
+
/// streamId-less stopped, which the id-less heuristic deliberately
|
|
1894
|
+
/// ignores — without this they would run out the 30s timeout instead of
|
|
1895
|
+
/// failing fast.
|
|
1753
1896
|
private func rejectPreemptedStreamStarts(winnerSeq: Int) {
|
|
1754
1897
|
for (streamId, start) in pendingStreamStarts where start.seq < winnerSeq {
|
|
1755
1898
|
pendingStreamStarts.removeValue(forKey: streamId)
|
|
@@ -1763,11 +1906,11 @@ public final class MentraBluetoothSDK {
|
|
|
1763
1906
|
}
|
|
1764
1907
|
}
|
|
1765
1908
|
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1909
|
+
/// The glasses process BLE commands FIFO, so this stop's ack also settles
|
|
1910
|
+
/// every start sent before it: whatever those starts brought up (or would
|
|
1911
|
+
/// have brought up) has been stopped, and no further status will arrive
|
|
1912
|
+
/// for them. Without this they would run out the 30s start timeout.
|
|
1913
|
+
/// Starts sent after the stop keep waiting for their own statuses.
|
|
1771
1914
|
private func rejectStreamStartsSuperseded(byStopSeq stopSeq: Int) {
|
|
1772
1915
|
for (streamId, start) in pendingStreamStarts where start.seq < stopSeq {
|
|
1773
1916
|
pendingStreamStarts.removeValue(forKey: streamId)
|
|
@@ -1969,9 +2112,9 @@ public final class MentraBluetoothSDK {
|
|
|
1969
2112
|
request.pending.resolve(results)
|
|
1970
2113
|
}
|
|
1971
2114
|
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
2115
|
+
/// scanId-echoing glasses: results for another scan are ignored instead of
|
|
2116
|
+
/// resolving the pending request, and matching chunks accumulate until the
|
|
2117
|
+
/// glasses flag the scan complete.
|
|
1975
2118
|
private func handleCorrelatedWifiScanChunk(
|
|
1976
2119
|
scanId: String,
|
|
1977
2120
|
results: [WifiScanResult],
|
|
@@ -1997,49 +2140,167 @@ public final class MentraBluetoothSDK {
|
|
|
1997
2140
|
}
|
|
1998
2141
|
|
|
1999
2142
|
private func handleWifiStatusForRequests(_ event: WifiStatusEvent) {
|
|
2000
|
-
|
|
2143
|
+
let connectRequest = pendingWifiStatus
|
|
2001
2144
|
// A wifi_status carrying the explicit error field is the glasses' failure
|
|
2002
2145
|
// verdict for the in-flight connect: reject now instead of running out the
|
|
2003
2146
|
// request timeout. Only the error field counts as failure — the glasses'
|
|
2004
2147
|
// connect sequence emits a debounced bare connected=false ~1-2s after
|
|
2005
2148
|
// credentials while association is still in progress, and rejecting on that
|
|
2006
2149
|
// would kill every connect attempt early.
|
|
2007
|
-
if
|
|
2008
|
-
if pendingWifiStatus ===
|
|
2150
|
+
if let connectRequest, let error = event.error {
|
|
2151
|
+
if pendingWifiStatus === connectRequest {
|
|
2009
2152
|
pendingWifiStatus = nil
|
|
2010
2153
|
}
|
|
2011
|
-
|
|
2154
|
+
connectRequest.pending.reject(
|
|
2012
2155
|
BluetoothSdkError(
|
|
2013
2156
|
code: error,
|
|
2014
|
-
message: "Glasses failed to join \"\(
|
|
2157
|
+
message: "Glasses failed to join \"\(connectRequest.ssid)\": \(error)"
|
|
2015
2158
|
)
|
|
2016
2159
|
)
|
|
2017
2160
|
return
|
|
2018
2161
|
}
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2162
|
+
if let connectRequest, wifiStatusMatchesConnect(event.status, ssid: connectRequest.ssid) {
|
|
2163
|
+
if pendingWifiStatus === connectRequest {
|
|
2164
|
+
pendingWifiStatus = nil
|
|
2165
|
+
}
|
|
2166
|
+
connectRequest.pending.resolve(event)
|
|
2167
|
+
return
|
|
2168
|
+
}
|
|
2169
|
+
|
|
2170
|
+
// Forget never settles from link state: modern requests require a correlated result,
|
|
2171
|
+
// while legacy requests resolve as unverified at accepted dispatch.
|
|
2172
|
+
}
|
|
2173
|
+
|
|
2174
|
+
private func handleWifiForgetResultForRequests(_ data: [String: Any]) {
|
|
2175
|
+
guard let request = pendingWifiForget,
|
|
2176
|
+
request.mode == .modern,
|
|
2177
|
+
request.epoch == wifiSessionCapabilities.epoch,
|
|
2178
|
+
case let .supported(version) = wifiSessionCapabilities.forgetResult,
|
|
2179
|
+
let result = parseWifiForgetResult(
|
|
2180
|
+
expectedRequestId: request.requestId,
|
|
2181
|
+
expectedSid: request.sid,
|
|
2182
|
+
expectedSsid: request.ssid,
|
|
2183
|
+
capabilityVersion: version,
|
|
2184
|
+
data: data
|
|
2185
|
+
)
|
|
2186
|
+
else { return }
|
|
2187
|
+
if pendingWifiForget === request, request.epoch == wifiSessionCapabilities.epoch {
|
|
2188
|
+
pendingWifiForget = nil
|
|
2189
|
+
}
|
|
2190
|
+
request.pending.resolve(result)
|
|
2191
|
+
}
|
|
2192
|
+
|
|
2193
|
+
private func handleSavedWifiNetworksForRequests(_ data: [String: Any]) {
|
|
2194
|
+
guard let request = pendingSavedWifiNetworks,
|
|
2195
|
+
request.mode == .modern,
|
|
2196
|
+
request.epoch == wifiSessionCapabilities.epoch,
|
|
2197
|
+
case let .supported(version) = wifiSessionCapabilities.savedNetworks,
|
|
2198
|
+
let result = parseSavedWifiNetworks(
|
|
2199
|
+
expectedRequestId: request.requestId,
|
|
2200
|
+
expectedSid: request.sid,
|
|
2201
|
+
capabilityVersion: version,
|
|
2202
|
+
data: data
|
|
2203
|
+
)
|
|
2204
|
+
else { return }
|
|
2205
|
+
if pendingSavedWifiNetworks === request, request.epoch == wifiSessionCapabilities.epoch {
|
|
2206
|
+
pendingSavedWifiNetworks = nil
|
|
2207
|
+
}
|
|
2208
|
+
request.pending.resolve(result)
|
|
2209
|
+
}
|
|
2210
|
+
|
|
2211
|
+
private func wifiStatusMatchesConnect(_ status: WifiStatus, ssid: String) -> Bool {
|
|
2212
|
+
if case let .connected(currentSsid, _) = status {
|
|
2213
|
+
return currentSsid == ssid
|
|
2214
|
+
}
|
|
2215
|
+
return false
|
|
2216
|
+
}
|
|
2217
|
+
|
|
2218
|
+
private func dispatchWifiForgetIfReady(_ request: PendingWifiForgetRequest) {
|
|
2219
|
+
guard pendingWifiForget === request,
|
|
2220
|
+
request.epoch == wifiSessionCapabilities.epoch,
|
|
2221
|
+
request.mode != .discovering,
|
|
2222
|
+
!request.commandSent
|
|
2223
|
+
else { return }
|
|
2224
|
+
if request.mode == .unsupported {
|
|
2225
|
+
request.pending.reject(BluetoothSdkError(code: "wifi_protocol_unsupported", message: "Unsupported WiFi forget protocol version."))
|
|
2226
|
+
return
|
|
2227
|
+
}
|
|
2228
|
+
request.sid = wifiSessionCapabilities.sessionId
|
|
2229
|
+
request.commandSent = DeviceManager.shared.forgetWifiNetwork(
|
|
2230
|
+
request.ssid,
|
|
2231
|
+
requestId: request.mode == .modern ? request.requestId : nil,
|
|
2232
|
+
sid: request.mode == .modern ? request.sid : nil
|
|
2233
|
+
)
|
|
2234
|
+
if !request.commandSent {
|
|
2235
|
+
request.pending.reject(BluetoothSdkError(code: "dispatch_failed", message: "No active glasses transport accepted WiFi forget."))
|
|
2236
|
+
} else if request.mode == .legacy {
|
|
2237
|
+
pendingWifiForget = nil
|
|
2238
|
+
request.pending.resolve(legacyWifiForgetResult(ssid: request.ssid))
|
|
2022
2239
|
}
|
|
2023
|
-
request.pending.resolve(event)
|
|
2024
2240
|
}
|
|
2025
2241
|
|
|
2026
|
-
private func
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2242
|
+
private func dispatchSavedWifiNetworksIfReady(_ request: PendingSavedWifiNetworks) {
|
|
2243
|
+
guard pendingSavedWifiNetworks === request,
|
|
2244
|
+
request.epoch == wifiSessionCapabilities.epoch,
|
|
2245
|
+
request.mode == .modern,
|
|
2246
|
+
!request.commandSent
|
|
2247
|
+
else { return }
|
|
2248
|
+
request.sid = wifiSessionCapabilities.sessionId
|
|
2249
|
+
request.commandSent = DeviceManager.shared.requestSavedWifiNetworks(requestId: request.requestId, sid: request.sid)
|
|
2250
|
+
if !request.commandSent {
|
|
2251
|
+
request.pending.reject(BluetoothSdkError(code: "dispatch_failed", message: "No active glasses transport accepted saved WiFi request."))
|
|
2252
|
+
}
|
|
2253
|
+
}
|
|
2254
|
+
|
|
2255
|
+
private func applyWifiProtocolCapabilities(_ data: [String: Any]) {
|
|
2256
|
+
wifiSessionCapabilities.applyVersionInfo1(data)
|
|
2257
|
+
if let request = pendingWifiForget,
|
|
2258
|
+
request.epoch == wifiSessionCapabilities.epoch,
|
|
2259
|
+
request.mode == .discovering
|
|
2260
|
+
{
|
|
2261
|
+
request.mode = wifiSessionCapabilities.forgetMode()
|
|
2262
|
+
dispatchWifiForgetIfReady(request)
|
|
2263
|
+
}
|
|
2264
|
+
if let request = pendingSavedWifiNetworks,
|
|
2265
|
+
request.epoch == wifiSessionCapabilities.epoch,
|
|
2266
|
+
request.mode == .discovering
|
|
2267
|
+
{
|
|
2268
|
+
request.mode = wifiSessionCapabilities.savedNetworksMode()
|
|
2269
|
+
if request.mode == .legacy || request.mode == .unsupported {
|
|
2270
|
+
pendingSavedWifiNetworks = nil
|
|
2271
|
+
request.pending.resolve(
|
|
2272
|
+
SavedWifiNetworksResult(
|
|
2273
|
+
outcome: .unsupported,
|
|
2274
|
+
networks: [],
|
|
2275
|
+
error: "saved_wifi_networks_unsupported"
|
|
2276
|
+
)
|
|
2277
|
+
)
|
|
2278
|
+
} else {
|
|
2279
|
+
dispatchSavedWifiNetworksIfReady(request)
|
|
2039
2280
|
}
|
|
2040
2281
|
}
|
|
2041
2282
|
}
|
|
2042
2283
|
|
|
2284
|
+
private func resetWifiProtocolSession(sessionId: String, code: String) {
|
|
2285
|
+
let error = BluetoothSdkError(code: code, message: "The glasses WiFi protocol session changed.")
|
|
2286
|
+
let wifiStatus = pendingWifiStatus?.pending
|
|
2287
|
+
let wifiForget = pendingWifiForget?.pending
|
|
2288
|
+
let savedNetworks = pendingSavedWifiNetworks?.pending
|
|
2289
|
+
let wifiScan = pendingWifiScan?.pending
|
|
2290
|
+
let hotspot = pendingHotspotStatus?.pending
|
|
2291
|
+
wifiSessionCapabilities.reset(sessionId: sessionId)
|
|
2292
|
+
pendingWifiStatus = nil
|
|
2293
|
+
pendingWifiForget = nil
|
|
2294
|
+
pendingSavedWifiNetworks = nil
|
|
2295
|
+
pendingWifiScan = nil
|
|
2296
|
+
pendingHotspotStatus = nil
|
|
2297
|
+
wifiStatus?.reject(error)
|
|
2298
|
+
wifiForget?.reject(error)
|
|
2299
|
+
savedNetworks?.reject(error)
|
|
2300
|
+
wifiScan?.reject(error)
|
|
2301
|
+
hotspot?.reject(error)
|
|
2302
|
+
}
|
|
2303
|
+
|
|
2043
2304
|
private func handleHotspotStatusForRequests(_ event: HotspotStatusEvent) {
|
|
2044
2305
|
guard let request = pendingHotspotStatus else { return }
|
|
2045
2306
|
guard hotspotStatusMatches(event.status, enabled: request.enabled) else { return }
|
|
@@ -2072,6 +2333,9 @@ public final class MentraBluetoothSDK {
|
|
|
2072
2333
|
private func dispatchStoreUpdate(_ category: String, _ changes: [String: Any]) {
|
|
2073
2334
|
switch ObservableStore.normalizeCategory(category) {
|
|
2074
2335
|
case "glasses":
|
|
2336
|
+
if changes["connected"] as? Bool == false {
|
|
2337
|
+
resetWifiProtocolSession(sessionId: "", code: "wifi_session_disconnected")
|
|
2338
|
+
}
|
|
2075
2339
|
analytics.observeGlassesStatus(glassesStatus)
|
|
2076
2340
|
let nextState = state
|
|
2077
2341
|
delegate?.mentraBluetoothSDK(self, didUpdate: nextState)
|
|
@@ -2116,7 +2380,8 @@ public final class MentraBluetoothSDK {
|
|
|
2116
2380
|
projectName: projectName
|
|
2117
2381
|
)
|
|
2118
2382
|
}
|
|
2119
|
-
|
|
2383
|
+
|
|
2384
|
+
private func dispatchDiscoveredDevices(_ rawSearchResults: Any?) {
|
|
2120
2385
|
guard let results = rawSearchResults as? [[String: Any]] else { return }
|
|
2121
2386
|
for result in results {
|
|
2122
2387
|
guard let name = result["name"] as? String else { continue }
|
|
@@ -2175,6 +2440,19 @@ private func dispatchDiscoveredDevices(_ rawSearchResults: Any?) {
|
|
|
2175
2440
|
activeSession.onComplete(activeSession.latestResults)
|
|
2176
2441
|
}
|
|
2177
2442
|
|
|
2443
|
+
private func handleVersionInfoForRequest(_ data: [String: Any]) {
|
|
2444
|
+
guard let request = pendingVersionInfo else { return }
|
|
2445
|
+
switch request.accumulator.accept(data) {
|
|
2446
|
+
case .ignored:
|
|
2447
|
+
break
|
|
2448
|
+
case .waiting:
|
|
2449
|
+
break
|
|
2450
|
+
case let .complete(result):
|
|
2451
|
+
pendingVersionInfo = nil
|
|
2452
|
+
request.pending.resolve(result)
|
|
2453
|
+
}
|
|
2454
|
+
}
|
|
2455
|
+
|
|
2178
2456
|
private func dispatchBridgeEvent(_ eventName: String, _ data: [String: Any]) {
|
|
2179
2457
|
switch eventName {
|
|
2180
2458
|
case "log":
|
|
@@ -2225,6 +2503,23 @@ private func dispatchDiscoveredDevices(_ rawSearchResults: Any?) {
|
|
|
2225
2503
|
let event = WifiStatusEvent(values: data)
|
|
2226
2504
|
handleWifiStatusForRequests(event)
|
|
2227
2505
|
delegate?.mentraBluetoothSDK(self, didReceive: .wifiStatus(event))
|
|
2506
|
+
case "wifi_forget_result":
|
|
2507
|
+
handleWifiForgetResultForRequests(data)
|
|
2508
|
+
delegate?.mentraBluetoothSDK(self, didReceive: .raw(name: eventName, values: data))
|
|
2509
|
+
case "saved_wifi_networks":
|
|
2510
|
+
handleSavedWifiNetworksForRequests(data)
|
|
2511
|
+
delegate?.mentraBluetoothSDK(self, didReceive: .raw(name: eventName, values: data))
|
|
2512
|
+
case "wifi_protocol_session_ready":
|
|
2513
|
+
resetWifiProtocolSession(
|
|
2514
|
+
sessionId: data["sid"] as? String ?? "",
|
|
2515
|
+
code: "wifi_session_restarted"
|
|
2516
|
+
)
|
|
2517
|
+
case "glasses_session_changed":
|
|
2518
|
+
resetWifiProtocolSession(
|
|
2519
|
+
sessionId: data["sid"] as? String ?? "",
|
|
2520
|
+
code: "wifi_session_changed"
|
|
2521
|
+
)
|
|
2522
|
+
delegate?.mentraBluetoothSDK(self, didReceive: .raw(name: eventName, values: data))
|
|
2228
2523
|
case "wifi_scan_result":
|
|
2229
2524
|
let networks = (data["networks"] as? [[String: Any]])?.map(WifiScanResult.init(values:)) ?? []
|
|
2230
2525
|
let hasCompletionFlag = data.keys.contains("scanComplete") || data.keys.contains("scan_complete")
|
|
@@ -2306,8 +2601,11 @@ private func dispatchDiscoveredDevices(_ rawSearchResults: Any?) {
|
|
|
2306
2601
|
handleSettingsAckForRequests(event)
|
|
2307
2602
|
delegate?.mentraBluetoothSDK(self, didReceive: .settingsAck(event))
|
|
2308
2603
|
case "version_info":
|
|
2604
|
+
if let infoType = data["versionInfoType"] as? String, ["version_info_1", "version_info"].contains(infoType) {
|
|
2605
|
+
applyWifiProtocolCapabilities(data)
|
|
2606
|
+
}
|
|
2309
2607
|
let event = VersionInfoResult(values: data)
|
|
2310
|
-
|
|
2608
|
+
handleVersionInfoForRequest(data)
|
|
2311
2609
|
delegate?.mentraBluetoothSDK(self, didReceive: .versionInfo(event))
|
|
2312
2610
|
case "compatible_glasses_search_stop":
|
|
2313
2611
|
delegate?.mentraBluetoothSDK(self, didStopScan: .completed)
|