@mentra/bluetooth-sdk 3.2.0-dev.200 → 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 +46 -45
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +5 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/Bridge.kt +63 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +5 -4
- 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 +285 -29
- package/android/src/main/java/com/mentra/bluetoothsdk/MessageAckPolicy.kt +4 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/WifiRequestResolution.kt +246 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/ClassicAudioConnectionTracker.kt +104 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.kt +312 -45
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/SGCManager.kt +2 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/status/DeviceStatus.kt +14 -0
- 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/WifiRequestResolutionTest.kt +271 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/ClassicAudioConnectionTrackerTest.kt +131 -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 +63 -0
- package/ios/Source/DeviceManager.swift +6 -3
- package/ios/Source/GeneratedChangelogCatalog.swift +1 -1
- package/ios/Source/GeneratedReleaseMetadata.swift +5 -5
- package/ios/Source/MentraBluetoothSDK.swift +365 -93
- package/ios/Source/WifiRequestResolution.swift +251 -0
- package/ios/Source/sgcs/MentraLive.swift +170 -35
- package/ios/Source/sgcs/SGCManager.swift +13 -1
- package/ios/Source/status/DeviceStatus.swift +28 -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
|
@@ -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] {
|
|
@@ -425,6 +441,18 @@ public struct VersionInfoResult: CustomStringConvertible {
|
|
|
425
441
|
if !packageName.isEmpty {
|
|
426
442
|
values["packageName"] = packageName
|
|
427
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
|
+
}
|
|
428
456
|
return values
|
|
429
457
|
}
|
|
430
458
|
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
@testable import MentraBluetoothSDK
|
|
2
|
+
import XCTest
|
|
3
|
+
|
|
4
|
+
final class WifiRequestResolutionTests: XCTestCase {
|
|
5
|
+
@MainActor
|
|
6
|
+
func testUnsupportedControllerDoesNotClaimWifiDispatch() {
|
|
7
|
+
let keys = ["fullyBooted", "connected", "connectionState", "micEnabled", "voiceActivityDetectionEnabled", "bluetoothClassicConnected"]
|
|
8
|
+
let snapshot = keys.compactMap { key in DeviceStore.shared.get("glasses", key).map { (key, $0) } }
|
|
9
|
+
defer { for (key, value) in snapshot {
|
|
10
|
+
DeviceStore.shared.apply("glasses", key, value)
|
|
11
|
+
} }
|
|
12
|
+
let controller: SGCManager = Simulated()
|
|
13
|
+
XCTAssertFalse(controller.forgetWifiNetwork("AP", requestId: nil, sid: nil))
|
|
14
|
+
XCTAssertFalse(controller.forgetWifiNetwork("AP", requestId: "request", sid: "sid"))
|
|
15
|
+
XCTAssertFalse(controller.requestSavedWifiNetworks(requestId: "request", sid: "sid"))
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
func testFutureFractionalAndMalformedCapabilitiesAreUnsupportedNotLegacy() {
|
|
19
|
+
for version in [0, 2, -1, 1.5, "1", true, NSNull()] as [Any] {
|
|
20
|
+
let capabilities = WifiSessionCapabilities()
|
|
21
|
+
capabilities.reset(sessionId: "sid")
|
|
22
|
+
capabilities.applyVersionInfo1(["wifiForgetResultVersion": version, "savedWifiNetworksVersion": version])
|
|
23
|
+
XCTAssertEqual(capabilities.forgetMode(), .unsupported)
|
|
24
|
+
XCTAssertEqual(capabilities.savedNetworksMode(), .unsupported)
|
|
25
|
+
}
|
|
26
|
+
let missingSession = WifiSessionCapabilities()
|
|
27
|
+
missingSession.applyVersionInfo1(["wifiForgetResultVersion": 1])
|
|
28
|
+
XCTAssertEqual(missingSession.forgetMode(), .unsupported)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
func testRawResponseRejectsPartialTuplesAndCoercionBeforeNormalization() {
|
|
32
|
+
let tuple: [String: Any] = ["protocol_version": 1, "requestId": "request", "sid": "sid"]
|
|
33
|
+
XCTAssertTrue(wifiResponseEnvelopeIsValid(tuple, allowLegacy: false))
|
|
34
|
+
for key in tuple.keys {
|
|
35
|
+
XCTAssertFalse(wifiResponseEnvelopeIsValid(tuple.filter { $0.key != key }, allowLegacy: true))
|
|
36
|
+
}
|
|
37
|
+
for version in [0, 2, 1.5, "1", true, NSNull()] as [Any] {
|
|
38
|
+
var malformed = tuple
|
|
39
|
+
malformed["protocol_version"] = version
|
|
40
|
+
XCTAssertFalse(wifiResponseEnvelopeIsValid(malformed, allowLegacy: true))
|
|
41
|
+
}
|
|
42
|
+
XCTAssertFalse(wifiResponseEnvelopeIsValid(tuple.merging(["connected": 0]) { _, new in new }, allowLegacy: true))
|
|
43
|
+
XCTAssertFalse(wifiResponseEnvelopeIsValid(tuple.merging(["dispatched": true]) { _, new in new }, allowLegacy: true))
|
|
44
|
+
XCTAssertTrue(wifiResponseEnvelopeIsValid(["dispatched": true], allowLegacy: true))
|
|
45
|
+
XCTAssertFalse(wifiResponseEnvelopeIsValid(["requestId": "", "dispatched": true], allowLegacy: true))
|
|
46
|
+
XCTAssertFalse(wifiResponseEnvelopeIsValid([:], allowLegacy: false))
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
func testSemanticResultsOmitTransportMetadata() throws {
|
|
50
|
+
let data: [String: Any] = ["requestId": "request", "sid": "sid", "protocolVersion": 1,
|
|
51
|
+
"ssid": "AP", "outcome": "confirmed", "networks": ["AP"]]
|
|
52
|
+
let forbidden: Set = ["mode", "capabilityVersion", "requestId", "sid", "protocolVersion"]
|
|
53
|
+
let forget = try XCTUnwrap(parseWifiForgetResult(expectedRequestId: "request", expectedSid: "sid", expectedSsid: "AP", capabilityVersion: 1, data: data))
|
|
54
|
+
let saved = try XCTUnwrap(parseSavedWifiNetworks(expectedRequestId: "request", expectedSid: "sid", capabilityVersion: 1, data: data))
|
|
55
|
+
XCTAssertTrue(Set(forget.values.keys).isDisjoint(with: forbidden))
|
|
56
|
+
XCTAssertTrue(Set(saved.values.keys).isDisjoint(with: forbidden))
|
|
57
|
+
for version in [1.5, true] as [Any] {
|
|
58
|
+
var malformed = data
|
|
59
|
+
malformed["protocolVersion"] = version
|
|
60
|
+
XCTAssertNil(parseWifiForgetResult(expectedRequestId: "request", expectedSid: "sid", expectedSsid: "AP", capabilityVersion: 1, data: malformed))
|
|
61
|
+
XCTAssertNil(parseSavedWifiNetworks(expectedRequestId: "request", expectedSid: "sid", capabilityVersion: 1, data: malformed))
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
@MainActor
|
|
66
|
+
func testPendingResponseCancellationResumesExactlyOnceWithoutTimeout() async {
|
|
67
|
+
let pending = PendingResponse<Int>(operation: "WiFi capability negotiation")
|
|
68
|
+
let waiter = Task { @MainActor in
|
|
69
|
+
try await pending.wait(timeoutMs: nil)
|
|
70
|
+
}
|
|
71
|
+
await Task.yield()
|
|
72
|
+
|
|
73
|
+
waiter.cancel()
|
|
74
|
+
|
|
75
|
+
do {
|
|
76
|
+
_ = try await waiter.value
|
|
77
|
+
XCTFail("Expected cancellation")
|
|
78
|
+
} catch let error as BluetoothSdkError {
|
|
79
|
+
XCTAssertEqual(error.code, "request_cancelled")
|
|
80
|
+
} catch {
|
|
81
|
+
XCTFail("Unexpected error: \(error)")
|
|
82
|
+
}
|
|
83
|
+
pending.resolve(42)
|
|
84
|
+
do {
|
|
85
|
+
_ = try await pending.wait(timeoutMs: nil)
|
|
86
|
+
XCTFail("Late resolve must not replace the cancellation result")
|
|
87
|
+
} catch let error as BluetoothSdkError {
|
|
88
|
+
XCTAssertEqual(error.code, "request_cancelled")
|
|
89
|
+
} catch {
|
|
90
|
+
XCTFail("Unexpected error: \(error)")
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
func testCapabilitiesAreUnknownPerSessionUntilVersionInfoFinalizesThem() {
|
|
95
|
+
let capabilities = WifiSessionCapabilities()
|
|
96
|
+
capabilities.reset(sessionId: "sid-1")
|
|
97
|
+
|
|
98
|
+
XCTAssertEqual(capabilities.forgetResult, .unknown)
|
|
99
|
+
XCTAssertEqual(capabilities.savedNetworks, .unknown)
|
|
100
|
+
capabilities.applyVersionInfo1(["sid": "sid-1", "wifiForgetResultVersion": 1])
|
|
101
|
+
|
|
102
|
+
XCTAssertEqual(capabilities.forgetResult, .supported(version: 1))
|
|
103
|
+
XCTAssertEqual(capabilities.savedNetworks, .legacy)
|
|
104
|
+
XCTAssertEqual(capabilities.forgetMode(), .modern)
|
|
105
|
+
XCTAssertEqual(capabilities.savedNetworksMode(), .legacy)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
func testNewSessionResetsCapabilitiesAndAdvancesEpoch() {
|
|
109
|
+
let capabilities = WifiSessionCapabilities()
|
|
110
|
+
capabilities.reset(sessionId: "sid-1")
|
|
111
|
+
capabilities.applyVersionInfo1([
|
|
112
|
+
"wifiForgetResultVersion": 1,
|
|
113
|
+
"savedWifiNetworksVersion": 1,
|
|
114
|
+
])
|
|
115
|
+
let oldEpoch = capabilities.epoch
|
|
116
|
+
|
|
117
|
+
capabilities.reset(sessionId: "sid-2")
|
|
118
|
+
|
|
119
|
+
XCTAssertGreaterThan(capabilities.epoch, oldEpoch)
|
|
120
|
+
XCTAssertEqual(capabilities.sessionId, "sid-2")
|
|
121
|
+
XCTAssertEqual(capabilities.forgetResult, .unknown)
|
|
122
|
+
XCTAssertEqual(capabilities.savedNetworks, .unknown)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
func testSsidValidationRejectsBlankInputWithoutChangingIdentity() {
|
|
126
|
+
XCTAssertFalse(wifiSsidIsValid(" "))
|
|
127
|
+
XCTAssertTrue(wifiSsidIsValid(" Field AP "))
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
func testForgetResultRequiresExactIdSessionSsidAndProtocol() {
|
|
131
|
+
let exact: [String: Any] = [
|
|
132
|
+
"requestId": "forget-1",
|
|
133
|
+
"sid": "sid-1",
|
|
134
|
+
"ssid": " Field AP ",
|
|
135
|
+
"protocolVersion": 1,
|
|
136
|
+
"outcome": "dispatched",
|
|
137
|
+
"connected": false,
|
|
138
|
+
]
|
|
139
|
+
|
|
140
|
+
XCTAssertNil(parseWifiForgetResult(
|
|
141
|
+
expectedRequestId: "other", expectedSid: "sid-1", expectedSsid: " Field AP ",
|
|
142
|
+
capabilityVersion: 1, data: exact
|
|
143
|
+
))
|
|
144
|
+
XCTAssertNil(parseWifiForgetResult(
|
|
145
|
+
expectedRequestId: "forget-1", expectedSid: "sid-1", expectedSsid: " Field AP ",
|
|
146
|
+
capabilityVersion: 1, data: exact.filter { $0.key != "requestId" }
|
|
147
|
+
))
|
|
148
|
+
XCTAssertNil(parseWifiForgetResult(
|
|
149
|
+
expectedRequestId: "forget-1", expectedSid: "other", expectedSsid: " Field AP ",
|
|
150
|
+
capabilityVersion: 1, data: exact
|
|
151
|
+
))
|
|
152
|
+
XCTAssertNil(parseWifiForgetResult(
|
|
153
|
+
expectedRequestId: "forget-1", expectedSid: "sid-1", expectedSsid: "Field AP",
|
|
154
|
+
capabilityVersion: 1, data: exact
|
|
155
|
+
))
|
|
156
|
+
XCTAssertNil(parseWifiForgetResult(
|
|
157
|
+
expectedRequestId: "forget-1", expectedSid: "sid-1", expectedSsid: " Field AP ",
|
|
158
|
+
capabilityVersion: 2, data: exact
|
|
159
|
+
))
|
|
160
|
+
XCTAssertEqual(
|
|
161
|
+
parseWifiForgetResult(
|
|
162
|
+
expectedRequestId: "forget-1", expectedSid: "sid-1", expectedSsid: " Field AP ",
|
|
163
|
+
capabilityVersion: 1, data: exact
|
|
164
|
+
)?.outcome,
|
|
165
|
+
.dispatched
|
|
166
|
+
)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
func testForgetParserPreservesEveryHonestTerminalOutcome() {
|
|
170
|
+
for outcome in ["confirmed", "dispatched", "not_found", "unsupported", "failed"] {
|
|
171
|
+
let parsed = parseWifiForgetResult(
|
|
172
|
+
expectedRequestId: "forget-1",
|
|
173
|
+
expectedSid: "sid-1",
|
|
174
|
+
expectedSsid: "AP",
|
|
175
|
+
capabilityVersion: 1,
|
|
176
|
+
data: [
|
|
177
|
+
"requestId": "forget-1",
|
|
178
|
+
"sid": "sid-1",
|
|
179
|
+
"ssid": "AP",
|
|
180
|
+
"protocolVersion": 1,
|
|
181
|
+
"outcome": outcome,
|
|
182
|
+
]
|
|
183
|
+
)
|
|
184
|
+
XCTAssertEqual(parsed?.outcome.rawValue, outcome)
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
func testMissingConnectivitySnapshotRemainsUnknown() {
|
|
189
|
+
let parsed = parseWifiForgetResult(
|
|
190
|
+
expectedRequestId: "forget-1",
|
|
191
|
+
expectedSid: "sid-1",
|
|
192
|
+
expectedSsid: "AP",
|
|
193
|
+
capabilityVersion: 1,
|
|
194
|
+
data: [
|
|
195
|
+
"requestId": "forget-1",
|
|
196
|
+
"sid": "sid-1",
|
|
197
|
+
"ssid": "AP",
|
|
198
|
+
"protocolVersion": 1,
|
|
199
|
+
"outcome": "dispatched",
|
|
200
|
+
]
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
XCTAssertNil(parsed?.connected)
|
|
204
|
+
XCTAssertNil(parsed?.values["connected"])
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
func testLegacyForgetIsExplicitlyUnverified() {
|
|
208
|
+
let result = legacyWifiForgetResult(
|
|
209
|
+
ssid: "AP"
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
XCTAssertTrue(Set(result.values.keys).isDisjoint(with: ["mode", "capabilityVersion", "requestId", "sid"]))
|
|
213
|
+
XCTAssertEqual(result.outcome, .legacyUnverified)
|
|
214
|
+
XCTAssertNil(result.connected)
|
|
215
|
+
XCTAssertNil(result.currentSsid)
|
|
216
|
+
XCTAssertNil(result.localIp)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
func testSavedListPreservesIdentityAndRequiresCorrelationTuple() {
|
|
220
|
+
let exact: [String: Any] = [
|
|
221
|
+
"requestId": "saved-1",
|
|
222
|
+
"sid": "sid-1",
|
|
223
|
+
"protocolVersion": 1,
|
|
224
|
+
"outcome": "confirmed",
|
|
225
|
+
"networks": [" Field AP ", "", "Field AP", " Field AP "],
|
|
226
|
+
]
|
|
227
|
+
|
|
228
|
+
XCTAssertNil(parseSavedWifiNetworks(
|
|
229
|
+
expectedRequestId: "other", expectedSid: "sid-1", capabilityVersion: 1, data: exact
|
|
230
|
+
))
|
|
231
|
+
XCTAssertNil(parseSavedWifiNetworks(
|
|
232
|
+
expectedRequestId: "saved-1", expectedSid: "sid-1", capabilityVersion: 1,
|
|
233
|
+
data: exact.filter { $0.key != "requestId" }
|
|
234
|
+
))
|
|
235
|
+
XCTAssertNil(parseSavedWifiNetworks(
|
|
236
|
+
expectedRequestId: "saved-1", expectedSid: "other", capabilityVersion: 1, data: exact
|
|
237
|
+
))
|
|
238
|
+
XCTAssertNil(parseSavedWifiNetworks(
|
|
239
|
+
expectedRequestId: "saved-1", expectedSid: "sid-1", capabilityVersion: 2, data: exact
|
|
240
|
+
))
|
|
241
|
+
XCTAssertEqual(
|
|
242
|
+
parseSavedWifiNetworks(
|
|
243
|
+
expectedRequestId: "saved-1", expectedSid: "sid-1", capabilityVersion: 1, data: exact
|
|
244
|
+
)?.networks,
|
|
245
|
+
[" Field AP ", "Field AP"]
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
var failure = exact
|
|
249
|
+
failure["outcome"] = "failed"
|
|
250
|
+
failure["error"] = "backend_failed"
|
|
251
|
+
failure["networks"] = [String]()
|
|
252
|
+
let parsedFailure = parseSavedWifiNetworks(
|
|
253
|
+
expectedRequestId: "saved-1",
|
|
254
|
+
expectedSid: "sid-1",
|
|
255
|
+
capabilityVersion: 1,
|
|
256
|
+
data: failure
|
|
257
|
+
)
|
|
258
|
+
XCTAssertEqual(parsedFailure?.outcome, .failed)
|
|
259
|
+
XCTAssertEqual(parsedFailure?.error, "backend_failed")
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
func testRawForgetEventPreservesModernAndLegacyWireTruth() {
|
|
264
|
+
let modern = normalizeWifiForgetResultEvent(
|
|
265
|
+
requestId: "forget-1",
|
|
266
|
+
sid: "sid-1",
|
|
267
|
+
ssid: "AP",
|
|
268
|
+
protocolVersion: 1,
|
|
269
|
+
outcome: "dispatched",
|
|
270
|
+
legacyDispatched: nil,
|
|
271
|
+
connected: false,
|
|
272
|
+
currentSsid: "",
|
|
273
|
+
localIp: "",
|
|
274
|
+
error: nil
|
|
275
|
+
)
|
|
276
|
+
let legacy = normalizeWifiForgetResultEvent(
|
|
277
|
+
requestId: "",
|
|
278
|
+
sid: "",
|
|
279
|
+
ssid: "AP",
|
|
280
|
+
protocolVersion: 0,
|
|
281
|
+
outcome: "",
|
|
282
|
+
legacyDispatched: true,
|
|
283
|
+
connected: false,
|
|
284
|
+
currentSsid: "",
|
|
285
|
+
localIp: "",
|
|
286
|
+
error: nil
|
|
287
|
+
)
|
|
288
|
+
let withoutSnapshot = normalizeWifiForgetResultEvent(
|
|
289
|
+
requestId: "forget-unknown",
|
|
290
|
+
sid: "sid-1",
|
|
291
|
+
ssid: "AP",
|
|
292
|
+
protocolVersion: 1,
|
|
293
|
+
outcome: "dispatched",
|
|
294
|
+
legacyDispatched: nil,
|
|
295
|
+
connected: nil,
|
|
296
|
+
currentSsid: "",
|
|
297
|
+
localIp: "",
|
|
298
|
+
error: nil
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
XCTAssertEqual(modern?["mode"] as? String, "modern")
|
|
302
|
+
XCTAssertEqual(modern?["outcome"] as? String, "dispatched")
|
|
303
|
+
XCTAssertEqual(legacy?["mode"] as? String, "legacy")
|
|
304
|
+
XCTAssertEqual(legacy?["dispatched"] as? Bool, true)
|
|
305
|
+
XCTAssertNil(legacy?["sid"])
|
|
306
|
+
XCTAssertNil(withoutSnapshot?["connected"])
|
|
307
|
+
XCTAssertNil(normalizeWifiForgetResultEvent(
|
|
308
|
+
requestId: "bad",
|
|
309
|
+
sid: "",
|
|
310
|
+
ssid: "AP",
|
|
311
|
+
protocolVersion: 0,
|
|
312
|
+
outcome: "",
|
|
313
|
+
legacyDispatched: nil,
|
|
314
|
+
connected: false,
|
|
315
|
+
currentSsid: "",
|
|
316
|
+
localIp: "",
|
|
317
|
+
error: nil
|
|
318
|
+
))
|
|
319
|
+
}
|
|
320
|
+
}
|
package/package.json
CHANGED
|
@@ -128,6 +128,68 @@ export type WifiStatusChangeEvent = WifiStatus & {
|
|
|
128
128
|
error?: string
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
+
export type WifiForgetOutcome =
|
|
132
|
+
| "confirmed"
|
|
133
|
+
| "dispatched"
|
|
134
|
+
| "not_found"
|
|
135
|
+
| "unsupported"
|
|
136
|
+
| "failed"
|
|
137
|
+
| "legacy_unverified"
|
|
138
|
+
|
|
139
|
+
export type WifiForgetResult = {
|
|
140
|
+
ssid: string
|
|
141
|
+
outcome: WifiForgetOutcome
|
|
142
|
+
connected?: boolean
|
|
143
|
+
currentSsid?: string
|
|
144
|
+
localIp?: string
|
|
145
|
+
error?: string
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export type ModernWifiForgetResultEvent = {
|
|
149
|
+
type: "wifi_forget_result"
|
|
150
|
+
mode: "modern"
|
|
151
|
+
requestId: string
|
|
152
|
+
sid: string
|
|
153
|
+
ssid: string
|
|
154
|
+
protocolVersion: number
|
|
155
|
+
outcome: Exclude<WifiForgetOutcome, "legacy_unverified">
|
|
156
|
+
connected?: boolean
|
|
157
|
+
currentSsid?: string
|
|
158
|
+
localIp?: string
|
|
159
|
+
error?: string
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export type LegacyWifiForgetResultEvent = {
|
|
163
|
+
type: "wifi_forget_result"
|
|
164
|
+
mode: "legacy"
|
|
165
|
+
ssid: string
|
|
166
|
+
dispatched: boolean
|
|
167
|
+
connected?: boolean
|
|
168
|
+
currentSsid?: string
|
|
169
|
+
localIp?: string
|
|
170
|
+
error?: string
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export type WifiForgetResultEvent = ModernWifiForgetResultEvent | LegacyWifiForgetResultEvent
|
|
174
|
+
|
|
175
|
+
export type SavedWifiNetworksOutcome = "confirmed" | "unsupported" | "failed"
|
|
176
|
+
|
|
177
|
+
export type SavedWifiNetworksResult = {
|
|
178
|
+
outcome: SavedWifiNetworksOutcome
|
|
179
|
+
networks: string[]
|
|
180
|
+
error?: string
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export type SavedWifiNetworksEvent = {
|
|
184
|
+
type: "saved_wifi_networks"
|
|
185
|
+
requestId: string
|
|
186
|
+
sid: string
|
|
187
|
+
protocolVersion: number
|
|
188
|
+
outcome: SavedWifiNetworksOutcome
|
|
189
|
+
networks: string[]
|
|
190
|
+
error?: string
|
|
191
|
+
}
|
|
192
|
+
|
|
131
193
|
export type HotspotStatus = {state: "disabled"} | {state: "enabled"; ssid: string; password: string; localIp: string}
|
|
132
194
|
|
|
133
195
|
export type EnabledHotspotStatus = Extract<HotspotStatus, {state: "enabled"}>
|
|
@@ -147,6 +209,8 @@ export type HotspotErrorEvent = {
|
|
|
147
209
|
}
|
|
148
210
|
|
|
149
211
|
export type VersionInfoResult = {
|
|
212
|
+
versionInfoType?: string
|
|
213
|
+
sid?: string
|
|
150
214
|
androidVersion: string
|
|
151
215
|
firmwareVersion: string
|
|
152
216
|
besFirmwareVersion: string
|
|
@@ -169,6 +233,10 @@ export type VersionInfoResult = {
|
|
|
169
233
|
packageName?: string
|
|
170
234
|
/** Phone-served hotspot OTA protocol version; 0 means unsupported/legacy glasses. */
|
|
171
235
|
hotspotOtaVersion: number
|
|
236
|
+
/** Session-correlated WiFi forget result protocol; absent/0 means unsupported. */
|
|
237
|
+
wifiForgetResultVersion?: number
|
|
238
|
+
/** Session-correlated saved-network listing protocol; absent/0 means unsupported. */
|
|
239
|
+
savedWifiNetworksVersion?: number
|
|
172
240
|
}
|
|
173
241
|
|
|
174
242
|
export type VersionInfoEvent = VersionInfoResult & {
|
|
@@ -930,6 +998,8 @@ export type BluetoothSdkModuleEvents = {
|
|
|
930
998
|
phone_notification_dismissed: (event: PhoneNotificationDismissedEvent) => void
|
|
931
999
|
wifi_status_change: (event: WifiStatusChangeEvent) => void
|
|
932
1000
|
wifi_scan_result: (event: WifiScanResultEvent) => void
|
|
1001
|
+
wifi_forget_result: (event: WifiForgetResultEvent) => void
|
|
1002
|
+
saved_wifi_networks: (event: SavedWifiNetworksEvent) => void
|
|
933
1003
|
hotspot_status_change: (event: HotspotStatusChangeEvent) => void
|
|
934
1004
|
hotspot_error: (event: HotspotErrorEvent) => void
|
|
935
1005
|
photo_response: (event: PhotoResponseEvent) => void
|
|
@@ -1090,6 +1160,8 @@ export type BluetoothSdkEventMap = {
|
|
|
1090
1160
|
local_transcription: LocalTranscriptionEvent
|
|
1091
1161
|
wifi_status_change: WifiStatusChangeEvent
|
|
1092
1162
|
wifi_scan_result: WifiScanResultEvent
|
|
1163
|
+
wifi_forget_result: WifiForgetResultEvent
|
|
1164
|
+
saved_wifi_networks: SavedWifiNetworksEvent
|
|
1093
1165
|
hotspot_status_change: HotspotStatusChangeEvent
|
|
1094
1166
|
hotspot_error: HotspotErrorEvent
|
|
1095
1167
|
photo_response: PhotoResponseEvent
|
|
@@ -1182,8 +1254,11 @@ export interface BluetoothSdkPublicModule {
|
|
|
1182
1254
|
ping(): Promise<void>
|
|
1183
1255
|
|
|
1184
1256
|
requestWifiScan(): Promise<WifiSearchResult[]>
|
|
1257
|
+
/** List exact WiFi SSIDs from glasses that support reliable saved-network enumeration. */
|
|
1258
|
+
getSavedWifiNetworks(): Promise<SavedWifiNetworksResult>
|
|
1185
1259
|
sendWifiCredentials(ssid: string, password: string): Promise<WifiStatusChangeEvent>
|
|
1186
|
-
|
|
1260
|
+
/** Forget a saved network and return its correlated semantic outcome. */
|
|
1261
|
+
forgetWifiNetwork(ssid: string): Promise<WifiForgetResult>
|
|
1187
1262
|
setHotspotState(enabled: boolean): Promise<HotspotStatusChangeEvent>
|
|
1188
1263
|
/** Set the glasses clock from the phone after an OTA clock-skew failure. */
|
|
1189
1264
|
setSystemTime(timestampMs: number): Promise<void>
|
|
@@ -55,6 +55,8 @@ import {
|
|
|
55
55
|
VideoRecordingStatusEvent,
|
|
56
56
|
VersionInfoResult,
|
|
57
57
|
WarmUpCameraParams,
|
|
58
|
+
SavedWifiNetworksResult,
|
|
59
|
+
WifiForgetResult,
|
|
58
60
|
WifiSearchResult,
|
|
59
61
|
WifiStatusChangeEvent,
|
|
60
62
|
} from "../BluetoothSdk.types"
|
|
@@ -125,8 +127,9 @@ declare class BluetoothSdkNativeModule extends NativeModule<BluetoothSdkModuleEv
|
|
|
125
127
|
|
|
126
128
|
// WiFi Commands
|
|
127
129
|
requestWifiScan(): Promise<WifiSearchResult[]>
|
|
130
|
+
getSavedWifiNetworks(): Promise<SavedWifiNetworksResult>
|
|
128
131
|
sendWifiCredentials(ssid: string, password: string): Promise<WifiStatusChangeEvent>
|
|
129
|
-
forgetWifiNetwork(ssid: string): Promise<
|
|
132
|
+
forgetWifiNetwork(ssid: string): Promise<WifiForgetResult>
|
|
130
133
|
setHotspotState(enabled: boolean): Promise<HotspotStatusChangeEvent>
|
|
131
134
|
/** Enable or disable Wi-Fi ADB on Mentra Live (no-op on other devices). */
|
|
132
135
|
setWifiAdbState(enabled: boolean): Promise<void>
|
|
@@ -3,7 +3,7 @@ export const GENERATED_RELEASE_CHANGELOGS = Object.freeze(
|
|
|
3
3
|
[
|
|
4
4
|
{
|
|
5
5
|
"version": "3.2.0",
|
|
6
|
-
"markdown": "This release is under active development. User-facing changes will be documented as they land
|
|
6
|
+
"markdown": "This release is under active development. User-facing changes will be documented as they land.\n\n- Breaking Bluetooth SDK change: `forgetWifiNetwork(ssid)` now returns `WifiForgetResult` instead of `WifiStatusChangeEvent`. Migrate status-only consumers to the semantic `outcome`; the requested network remains `ssid`, while optional post-command connectivity is reported by `connected`, `currentSsid`, and `localIp`."
|
|
7
7
|
},
|
|
8
8
|
{
|
|
9
9
|
"version": "3.1.0",
|
|
@@ -12,9 +12,9 @@ export interface BluetoothSdkReleaseMetadata {
|
|
|
12
12
|
export const BLUETOOTH_SDK_RELEASE_METADATA: Readonly<BluetoothSdkReleaseMetadata> = Object.freeze({
|
|
13
13
|
"schemaVersion": 1,
|
|
14
14
|
"familyBaseVersion": "3.2.0",
|
|
15
|
-
"releaseIdentity": "3.2.0-dev.
|
|
16
|
-
"releaseSetId": "mentra-3.2.0-dev.
|
|
17
|
-
"sourceCommit": "
|
|
18
|
-
"otaManifestUrl": "https://github.com/Mentra-Community/MentraOS/releases/download/mentra-builds-v3.2.0/mentra-live-ota-3.2.0-dev.
|
|
19
|
-
"otaManifestSha256": "
|
|
15
|
+
"releaseIdentity": "3.2.0-dev.203",
|
|
16
|
+
"releaseSetId": "mentra-3.2.0-dev.203",
|
|
17
|
+
"sourceCommit": "7eb4deefff27d498c2b4562d628607f399aa1f40",
|
|
18
|
+
"otaManifestUrl": "https://github.com/Mentra-Community/MentraOS/releases/download/mentra-builds-v3.2.0/mentra-live-ota-3.2.0-dev.203.json",
|
|
19
|
+
"otaManifestSha256": "0ae026238c8e70970be4e181cc1ae73dd905418292edebddb610012f4f0a9b6f"
|
|
20
20
|
})
|
package/src/index.ts
CHANGED
|
@@ -26,6 +26,8 @@ const PUBLIC_EVENT_NAMES = new Set<BluetoothSdkEventName>([
|
|
|
26
26
|
"local_transcription",
|
|
27
27
|
"wifi_status_change",
|
|
28
28
|
"wifi_scan_result",
|
|
29
|
+
"wifi_forget_result",
|
|
30
|
+
"saved_wifi_networks",
|
|
29
31
|
"hotspot_status_change",
|
|
30
32
|
"hotspot_error",
|
|
31
33
|
"photo_response",
|
|
@@ -117,6 +119,7 @@ export const BluetoothSdk: BluetoothSdkPublicModule = Object.freeze({
|
|
|
117
119
|
setScreenDisabled: bindPublicMethod("setScreenDisabled"),
|
|
118
120
|
ping: bindPublicMethod("ping"),
|
|
119
121
|
requestWifiScan: bindPublicMethod("requestWifiScan"),
|
|
122
|
+
getSavedWifiNetworks: bindPublicMethod("getSavedWifiNetworks"),
|
|
120
123
|
sendWifiCredentials: bindPublicMethod("sendWifiCredentials"),
|
|
121
124
|
forgetWifiNetwork: bindPublicMethod("forgetWifiNetwork"),
|
|
122
125
|
setHotspotState: bindPublicMethod("setHotspotState"),
|
|
@@ -308,6 +311,14 @@ export type {
|
|
|
308
311
|
VideoRecordingSuccessStatusEvent,
|
|
309
312
|
VersionInfoEvent,
|
|
310
313
|
VersionInfoResult,
|
|
314
|
+
SavedWifiNetworksEvent,
|
|
315
|
+
SavedWifiNetworksOutcome,
|
|
316
|
+
SavedWifiNetworksResult,
|
|
317
|
+
WifiForgetOutcome,
|
|
318
|
+
WifiForgetResult,
|
|
319
|
+
WifiForgetResultEvent,
|
|
320
|
+
LegacyWifiForgetResultEvent,
|
|
321
|
+
ModernWifiForgetResultEvent,
|
|
311
322
|
WifiScanResultEvent,
|
|
312
323
|
VoiceActivityDetectionStatusEvent,
|
|
313
324
|
WifiSearchResult,
|