@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
package/README.md
CHANGED
|
@@ -115,6 +115,24 @@ Android apps should request the permissions required by the features they use:
|
|
|
115
115
|
|
|
116
116
|
Some Android 12+ devices require Location permission and Location services before BLE scan callbacks are delivered.
|
|
117
117
|
|
|
118
|
+
### Android Foreground Service Types
|
|
119
|
+
|
|
120
|
+
The SDK service reads its allowed types from the merged Android manifest.
|
|
121
|
+
The default manifest retains MentraOS's connected-device, microphone, location,
|
|
122
|
+
media-playback and data-sync capabilities. A Bluetooth-only host can override
|
|
123
|
+
`com.mentra.bluetoothsdk.services.ForegroundService` to use `connectedDevice`
|
|
124
|
+
with `tools:replace="android:foregroundServiceType"`, and remove unused typed
|
|
125
|
+
FGS permissions from its final manifest. Startup then uses `connectedDevice`
|
|
126
|
+
instead of `dataSync`; the SDK's `CHANGE_WIFI_STATE` permission satisfies its
|
|
127
|
+
startup prerequisite even before a Bluetooth runtime permission is granted.
|
|
128
|
+
|
|
129
|
+
Only restrict types when the corresponding background features are unused.
|
|
130
|
+
Receiving the glasses' BLE audio is distinct from selecting Android's phone or
|
|
131
|
+
Bluetooth headset microphone. Hosts using Android microphone capture, location
|
|
132
|
+
tracking or background media playback must retain the corresponding types and
|
|
133
|
+
permissions, including those required by other native modules. At least
|
|
134
|
+
`connectedDevice` or `dataSync` must remain for SDK service startup.
|
|
135
|
+
|
|
118
136
|
iOS apps should include usage descriptions:
|
|
119
137
|
|
|
120
138
|
```json
|
|
@@ -134,21 +152,19 @@ iOS apps should include usage descriptions:
|
|
|
134
152
|
Use `scan()` when your app needs to show a picker. It calls `onResults` every time the discovered list changes, then resolves with the final list after the timeout.
|
|
135
153
|
|
|
136
154
|
```ts
|
|
137
|
-
import BluetoothSdk, {
|
|
138
|
-
DeviceModels,
|
|
139
|
-
} from '@mentra/bluetooth-sdk'
|
|
155
|
+
import BluetoothSdk, {DeviceModels} from "@mentra/bluetooth-sdk"
|
|
140
156
|
|
|
141
157
|
const devices = await BluetoothSdk.scan(DeviceModels.MentraLive, {
|
|
142
158
|
timeoutMs: 10_000,
|
|
143
159
|
onResults: (nextDevices) => {
|
|
144
|
-
console.log(
|
|
160
|
+
console.log("Nearby glasses:", nextDevices)
|
|
145
161
|
},
|
|
146
162
|
onDiagnostic: (hint) => showScanHint(hint.message),
|
|
147
163
|
})
|
|
148
164
|
|
|
149
165
|
const device = await chooseDevice(devices)
|
|
150
166
|
if (!device) {
|
|
151
|
-
throw new Error(
|
|
167
|
+
throw new Error("No Mentra Live glasses selected")
|
|
152
168
|
}
|
|
153
169
|
|
|
154
170
|
await BluetoothSdk.connect(device)
|
|
@@ -257,9 +273,9 @@ subpath for common lifecycle plumbing. Use the root `BluetoothSdk` object for
|
|
|
257
273
|
commands such as `requestPhoto()`, `startStream()`, and `setMicState()`.
|
|
258
274
|
|
|
259
275
|
```tsx
|
|
260
|
-
import {Button, Text, View} from
|
|
261
|
-
import {DeviceModels} from
|
|
262
|
-
import {useBluetoothEvent, useMentraBluetooth} from
|
|
276
|
+
import {Button, Text, View} from "react-native"
|
|
277
|
+
import {DeviceModels} from "@mentra/bluetooth-sdk"
|
|
278
|
+
import {useBluetoothEvent, useMentraBluetooth} from "@mentra/bluetooth-sdk/react"
|
|
263
279
|
|
|
264
280
|
export function DeviceScreen() {
|
|
265
281
|
const mentra = useMentraBluetooth({
|
|
@@ -267,13 +283,13 @@ export function DeviceScreen() {
|
|
|
267
283
|
scanTimeoutMs: 10_000,
|
|
268
284
|
})
|
|
269
285
|
|
|
270
|
-
useBluetoothEvent(
|
|
271
|
-
console.log(
|
|
286
|
+
useBluetoothEvent("button_press", (event) => {
|
|
287
|
+
console.log("Glasses button:", event.buttonId, event.pressType)
|
|
272
288
|
})
|
|
273
289
|
|
|
274
290
|
return (
|
|
275
291
|
<View>
|
|
276
|
-
<Text>{mentra.glasses.connected ?
|
|
292
|
+
<Text>{mentra.glasses.connected ? "Connected" : "Disconnected"}</Text>
|
|
277
293
|
<Button disabled={mentra.busy} title="Scan" onPress={() => mentra.scan.start()} />
|
|
278
294
|
{mentra.scan.devices.map((device) => (
|
|
279
295
|
<Button key={device.id} title={device.name} onPress={() => mentra.connect(device)} />
|
|
@@ -296,11 +312,11 @@ The React hook exposes `glasses.connection` as a discriminated union:
|
|
|
296
312
|
|
|
297
313
|
```ts
|
|
298
314
|
type GlassesConnectionStatus =
|
|
299
|
-
| {state:
|
|
300
|
-
| {state:
|
|
301
|
-
| {state:
|
|
302
|
-
| {state:
|
|
303
|
-
| {state:
|
|
315
|
+
| {state: "disconnected"}
|
|
316
|
+
| {state: "scanning"}
|
|
317
|
+
| {state: "connecting"}
|
|
318
|
+
| {state: "bonding"}
|
|
319
|
+
| {state: "connected"; fullyBooted: boolean}
|
|
304
320
|
```
|
|
305
321
|
|
|
306
322
|
Use `connection.state` for link progress. `fullyBooted` only exists when `state === 'connected'`. Android and iOS native APIs also keep `connectionState`, `connected`, and `fullyBooted` as native status properties for Kotlin and Swift callers.
|
|
@@ -356,11 +372,14 @@ await BluetoothSdk.setDashboardPosition(4, 2)
|
|
|
356
372
|
const networks = await BluetoothSdk.requestWifiScan()
|
|
357
373
|
console.log(networks.map((network) => network.ssid))
|
|
358
374
|
|
|
359
|
-
const
|
|
375
|
+
const savedResult = await BluetoothSdk.getSavedWifiNetworks()
|
|
376
|
+
if (savedResult.outcome === "confirmed") console.log(savedResult.networks)
|
|
377
|
+
|
|
378
|
+
const wifiStatus = await BluetoothSdk.sendWifiCredentials("Office WiFi", "secret")
|
|
360
379
|
console.log(wifiStatus.state)
|
|
361
380
|
|
|
362
|
-
const
|
|
363
|
-
console.log(
|
|
381
|
+
const forgetResult = await BluetoothSdk.forgetWifiNetwork("Office WiFi")
|
|
382
|
+
console.log(forgetResult.outcome)
|
|
364
383
|
|
|
365
384
|
const hotspotStatus = await BluetoothSdk.setHotspotState(true)
|
|
366
385
|
console.log(hotspotStatus.state)
|
|
@@ -371,19 +390,11 @@ const galleryAck = await BluetoothSdk.setGalleryModeEnabled(true)
|
|
|
371
390
|
console.log(galleryAck.status)
|
|
372
391
|
await BluetoothSdk.setGalleryModeEnabled(false)
|
|
373
392
|
|
|
374
|
-
await BluetoothSdk.setPreferredMic(
|
|
393
|
+
await BluetoothSdk.setPreferredMic("auto")
|
|
375
394
|
await BluetoothSdk.setMicState(true)
|
|
376
395
|
await BluetoothSdk.setOwnAppAudioPlaying(false)
|
|
377
396
|
|
|
378
|
-
const ledAck = await BluetoothSdk.rgbLedControl(
|
|
379
|
-
`led-${Date.now()}`,
|
|
380
|
-
'com.example.app',
|
|
381
|
-
'on',
|
|
382
|
-
'green',
|
|
383
|
-
500,
|
|
384
|
-
500,
|
|
385
|
-
3,
|
|
386
|
-
)
|
|
397
|
+
const ledAck = await BluetoothSdk.rgbLedControl(`led-${Date.now()}`, "com.example.app", "on", "green", 500, 500, 3)
|
|
387
398
|
console.log(ledAck.state)
|
|
388
399
|
```
|
|
389
400
|
|
|
@@ -422,7 +433,15 @@ await sdk.setDashboardContent("")
|
|
|
422
433
|
Settings commands that return `SettingsAckSuccessEvent` reject when the ASG reports an error ack. The SDK updates its local settings store only after that ASG ack resolves successfully, so observed SDK state reflects the acknowledged glasses state rather than a queued request. Raw `settings_ack` listener events still use `SettingsAckEvent` because they can include both success and failure statuses. `rgbLedControl(...)` resolves from a successful ASG `rgb_led_control_response` and rejects when the ASG reports `state: "error"`; raw `settings_ack` and `rgb_led_control_response` events remain available through listeners.
|
|
423
434
|
|
|
424
435
|
WiFi, hotspot, and version-info commands resolve from the ASG response path, not local dispatch:
|
|
425
|
-
`requestWifiScan()` resolves from the ASG `wifi_scan_result` completion response with the updated scan list, including `[]` when no networks are found. Intermediate `wifi_scan_result` events can arrive with `scanComplete: false` while the glasses stream discovered networks; the final event uses `scanComplete: true`. If older glasses stream non-empty scan results but never send the completion event, the request resolves with the accumulated scan list when the request times out. `sendWifiCredentials()` resolves when the requested SSID is connected, `forgetWifiNetwork()`
|
|
436
|
+
`requestWifiScan()` resolves from the ASG `wifi_scan_result` completion response with the updated scan list, including `[]` when no networks are found. Intermediate `wifi_scan_result` events can arrive with `scanComplete: false` while the glasses stream discovered networks; the final event uses `scanComplete: true`. If older glasses stream non-empty scan results but never send the completion event, the request resolves with the accumulated scan list when the request times out. `sendWifiCredentials()` resolves when the requested SSID is connected, `forgetWifiNetwork()` returns a semantic `WifiForgetResult`, `getSavedWifiNetworks()` returns a semantic `SavedWifiNetworksResult`, `setHotspotState()` resolves when the requested hotspot state is reported, and `requestVersionInfo()` waits for all chunks declared by the correlated response's `chunkCount`, `chunkIndex`, and `final` metadata. Legacy single-message responses complete immediately; legacy chunked responses complete on `version_info_3` after `version_info_1`. Missing final chunks time out rather than returning partial data after a quiet period.
|
|
437
|
+
|
|
438
|
+
Current Mentra Live builds advertise `wifiForgetResultVersion` and `savedWifiNetworksVersion` in `version_info_1`. Native queues calls while those capabilities are unknown, sharing one 15-second deadline for negotiation and response. A discovery timeout rejects and never guesses that the glasses are legacy. Only version 1 is supported: modern commands carry `protocolVersion: 1`, a nonempty request ID, and the glasses process session ID; legacy forget commands omit all three fields. Unknown or malformed versions fail closed. Correlated results must match the complete tuple and (for forget) exact SSID; unrelated `wifi_status` events never settle a modern request. Session changes and disconnects reject pending WiFi work instead of allowing stale responses to cross sessions.
|
|
439
|
+
|
|
440
|
+
`WifiForgetResult.outcome` is `confirmed`, `dispatched`, `not_found`, `unsupported`, `failed`, or `legacy_unverified`. K900 returns `dispatched`: ASG queued its asynchronous SystemUI broadcast, but the vendor API provides no completion callback and credential removal is not verified. `connected`, `currentSsid`, and `localIp` are best-effort diagnostic snapshot fields; `connected` is omitted when ASG cannot read the link state and must not be interpreted as `false`. Older firmware without the capability advertisement uses the isolated legacy path; accepted native dispatch immediately returns `legacy_unverified`, with no invented link-state snapshot. `getSavedWifiNetworks()` preserves exact SSID identity. It returns `confirmed` only for a backend with reliable enumeration, while K900 and legacy firmware return a typed `unsupported` result because the vendor credential store has no reliable list response.
|
|
441
|
+
|
|
442
|
+
Promise results expose semantic outcomes and optional link snapshots, not `mode`, `capabilityVersion`, `requestId`, or `sid`. A legacy result returns `legacy_unverified` immediately after actual native transport acceptance; no active or compatible glasses transport rejects with `dispatch_failed`.
|
|
443
|
+
|
|
444
|
+
Raw `wifi_forget_result` listeners receive a discriminated event union. `mode: "modern"` carries `requestId`, `sid`, `protocolVersion`, and semantic `outcome`; `mode: "legacy"` preserves the older uncorrelated wire frame's `dispatched` boolean. Partial tuples are rejected. Terminal Wi-Fi events use at-least-once BLE delivery, so raw listeners can observe retries; deduplicate modern events by `(requestId, sid)`. Promise coordinators accept only the first matching terminal frame.
|
|
426
445
|
|
|
427
446
|
The SDK automatically sends the phone wall clock once shortly after a glasses connection becomes ready. It waits for the initial command burst to drain before timestamping the command so startup queue delay does not become clock skew. The once-per-connection guard resets after disconnect, so every successful reconnect synchronizes again. The SDK does not periodically verify or correct clock skew during a long-lived connection; apps that require periodic reconciliation can compare `requestVersionInfo().systemTimeMs` with the phone clock.
|
|
428
447
|
|
|
@@ -507,15 +526,15 @@ Mentra Live also rejects `ota_start` before acknowledgement when its known batte
|
|
|
507
526
|
|
|
508
527
|
```ts
|
|
509
528
|
const photo = await BluetoothSdk.requestPhoto({
|
|
510
|
-
size:
|
|
511
|
-
webhookUrl:
|
|
512
|
-
authToken:
|
|
513
|
-
compress:
|
|
529
|
+
size: "medium",
|
|
530
|
+
webhookUrl: "https://api.example.com/mentra/photo",
|
|
531
|
+
authToken: "optional-token",
|
|
532
|
+
compress: "medium",
|
|
514
533
|
sound: true,
|
|
515
534
|
exposureTimeNs: null, // auto exposure; pass a positive nanosecond value for manual exposure
|
|
516
535
|
iso: null, // auto ISO; pass a positive ISO only with manual exposureTimeNs
|
|
517
536
|
})
|
|
518
|
-
console.log(
|
|
537
|
+
console.log("photo delivered", photo.photoUrl ?? photo.uploadUrl, photo.fileSizeBytes)
|
|
519
538
|
```
|
|
520
539
|
|
|
521
540
|
`requestPhoto(...)` resolves only after the full photo action reaches terminal success: capture completed and the photo was delivered to the webhook, either directly from the glasses over Wi-Fi or through the phone's Bluetooth fallback relay. If you omit `requestId`, the SDK generates one and the terminal response includes it. It rejects if the ASG reports `state: "error"`, if phone-side fallback upload fails, if the SDK cannot send the command, or if no terminal `photo_response` arrives within 30 seconds. Photo requests use this longer operation-specific deadline because max-quality BLE fallback can legitimately exceed the 15-second deadline used by ordinary commands. Use `photo_status` for intermediate stages such as `accepted`, `configuring`, `capturing`, `captured`, `uploading`, `ble_fallback_compression`, `ready_for_transfer`, and `transferring`; `photo_status` is progress, while `photo_response` is terminal success/error. The raw `photo_response` event stream still includes both success and error events for subscribers. The webhook should accept multipart form data with a `photo` file and `requestId`. If `authToken` is provided, the uploader adds `Authorization: Bearer <token>`. The camera light is always enabled for photo capture.
|
|
@@ -536,8 +555,8 @@ The missing/factory persistent FOV base is 102 degrees with centered ROI; existi
|
|
|
536
555
|
const streamId = `stream-${Date.now()}`
|
|
537
556
|
|
|
538
557
|
await BluetoothSdk.startStream({
|
|
539
|
-
type:
|
|
540
|
-
streamUrl:
|
|
558
|
+
type: "start_stream",
|
|
559
|
+
streamUrl: "http://192.168.1.42:8889/mentra-live/whip",
|
|
541
560
|
streamId,
|
|
542
561
|
video: {fps: 15},
|
|
543
562
|
})
|
|
@@ -553,19 +572,19 @@ Use `rtmp://` or `rtmps://` for RTMP, `srt://` for SRT, and `http://` or `https:
|
|
|
553
572
|
React Native components should use `useBluetoothEvent()` for hardware events:
|
|
554
573
|
|
|
555
574
|
```tsx
|
|
556
|
-
import {useBluetoothEvent} from
|
|
575
|
+
import {useBluetoothEvent} from "@mentra/bluetooth-sdk/react"
|
|
557
576
|
|
|
558
577
|
export function HardwareEventLogger() {
|
|
559
|
-
useBluetoothEvent(
|
|
560
|
-
useBluetoothEvent(
|
|
561
|
-
useBluetoothEvent(
|
|
562
|
-
useBluetoothEvent(
|
|
563
|
-
useBluetoothEvent(
|
|
564
|
-
useBluetoothEvent(
|
|
578
|
+
useBluetoothEvent("button_press", (event) => console.log(event))
|
|
579
|
+
useBluetoothEvent("touch_event", (event) => console.log(event))
|
|
580
|
+
useBluetoothEvent("photo_status", (event) => console.log(event.status, event.resolvedConfig, event.captureMetadata))
|
|
581
|
+
useBluetoothEvent("stream_status", (event) => console.log(event))
|
|
582
|
+
useBluetoothEvent("speaking_status", (event) => console.log(event.speaking))
|
|
583
|
+
useBluetoothEvent("mic_pcm", (event) => {
|
|
565
584
|
console.log(event.sampleRate, event.bitsPerSample, event.channels, event.encoding)
|
|
566
585
|
console.log(event.pcm)
|
|
567
586
|
})
|
|
568
|
-
useBluetoothEvent(
|
|
587
|
+
useBluetoothEvent("mic_health", (event) => {
|
|
569
588
|
console.log(event.reason, event.sequenceGapEvents, event.decodeFailures)
|
|
570
589
|
})
|
|
571
590
|
|
|
@@ -339,6 +339,8 @@ class BluetoothSdkModule : Module() {
|
|
|
339
339
|
"local_transcription",
|
|
340
340
|
"wifi_status_change",
|
|
341
341
|
"wifi_scan_result",
|
|
342
|
+
"wifi_forget_result",
|
|
343
|
+
"saved_wifi_networks",
|
|
342
344
|
"hotspot_status_change",
|
|
343
345
|
"hotspot_error",
|
|
344
346
|
"photo_response",
|
|
@@ -592,12 +594,14 @@ class BluetoothSdkModule : Module() {
|
|
|
592
594
|
|
|
593
595
|
SdkCoroutineFunction("requestWifiScan") { -> requireSdk().requestWifiScan().map { it.toMap() } }
|
|
594
596
|
|
|
597
|
+
SdkCoroutineFunction("getSavedWifiNetworks") { -> requireSdk().getSavedWifiNetworks().toMap() }
|
|
598
|
+
|
|
595
599
|
SdkCoroutineFunction("sendWifiCredentials") { ssid: String, password: String ->
|
|
596
600
|
requireSdk().sendWifiCredentials(ssid, password).values
|
|
597
601
|
}
|
|
598
602
|
|
|
599
603
|
SdkCoroutineFunction("forgetWifiNetwork") { ssid: String ->
|
|
600
|
-
requireSdk().forgetWifiNetwork(ssid).
|
|
604
|
+
requireSdk().forgetWifiNetwork(ssid).toMap()
|
|
601
605
|
}
|
|
602
606
|
|
|
603
607
|
SdkCoroutineFunction("setHotspotState") { enabled: Boolean ->
|
|
@@ -476,11 +476,26 @@ public class Bridge private constructor() {
|
|
|
476
476
|
}
|
|
477
477
|
|
|
478
478
|
@JvmStatic
|
|
479
|
-
|
|
479
|
+
@JvmOverloads
|
|
480
|
+
fun sendVersionInfo(values: Map<String, Any>, responseChunk: String = "version_info") {
|
|
480
481
|
fun stringField(vararg keys: String): String =
|
|
481
482
|
keys.firstNotNullOfOrNull { key -> values[key] as? String } ?: ""
|
|
482
483
|
val body = HashMap<String, Any>()
|
|
483
484
|
body["type"] = "version_info"
|
|
485
|
+
body[VersionInfoResponseAccumulator.RESPONSE_CHUNK_KEY] = responseChunk
|
|
486
|
+
mapOf(
|
|
487
|
+
"chunkIndex" to VersionInfoResponseAccumulator.RESPONSE_INDEX_KEY,
|
|
488
|
+
"chunkCount" to VersionInfoResponseAccumulator.RESPONSE_COUNT_KEY,
|
|
489
|
+
"final" to VersionInfoResponseAccumulator.RESPONSE_FINAL_KEY,
|
|
490
|
+
"sid" to VersionInfoResponseAccumulator.RESPONSE_SID_KEY,
|
|
491
|
+
).forEach { (wireKey, internalKey) -> values[wireKey]?.let { body[internalKey] = it } }
|
|
492
|
+
(values["requestId"] as? String ?: values["request_id"] as? String)
|
|
493
|
+
?.takeIf { it.isNotEmpty() }
|
|
494
|
+
?.let {
|
|
495
|
+
body[VersionInfoResponseAccumulator.RESPONSE_REQUEST_ID_KEY] = it
|
|
496
|
+
}
|
|
497
|
+
body["versionInfoType"] = stringField("versionInfoType", "version_info_type")
|
|
498
|
+
body["sid"] = stringField("sid")
|
|
484
499
|
body["androidVersion"] = stringField("androidVersion", "android_version")
|
|
485
500
|
body["firmwareVersion"] = stringField("firmwareVersion", "firmware_version")
|
|
486
501
|
body["besFirmwareVersion"] = stringField("besFirmwareVersion", "bes_fw_version")
|
|
@@ -499,6 +514,15 @@ public class Bridge private constructor() {
|
|
|
499
514
|
(values["hotspotOtaVersion"] as? Number
|
|
500
515
|
?: values["hotspot_ota_version"] as? Number)
|
|
501
516
|
?.let { body["hotspotOtaVersion"] = it.toInt() }
|
|
517
|
+
for ((key, wireKey) in listOf(
|
|
518
|
+
"wifiForgetResultVersion" to "wifi_forget_result_version",
|
|
519
|
+
"savedWifiNetworksVersion" to "saved_wifi_networks_version",
|
|
520
|
+
)) {
|
|
521
|
+
if (values.containsKey(key) || values.containsKey(wireKey)) {
|
|
522
|
+
// Preserve malformed presence: it must not become legacy or be rounded to v1.
|
|
523
|
+
body[key] = values[key] ?: values[wireKey] ?: -1
|
|
524
|
+
}
|
|
525
|
+
}
|
|
502
526
|
sendTypedMessage("version_info", body)
|
|
503
527
|
}
|
|
504
528
|
|
|
@@ -579,6 +603,58 @@ public class Bridge private constructor() {
|
|
|
579
603
|
sendTypedMessage("wifi_status_change", payload)
|
|
580
604
|
}
|
|
581
605
|
|
|
606
|
+
@JvmStatic
|
|
607
|
+
fun sendWifiForgetResult(
|
|
608
|
+
requestId: String,
|
|
609
|
+
sid: String,
|
|
610
|
+
ssid: String,
|
|
611
|
+
protocolVersion: Int,
|
|
612
|
+
outcome: String,
|
|
613
|
+
legacyDispatched: Boolean?,
|
|
614
|
+
connected: Boolean?,
|
|
615
|
+
currentSsid: String,
|
|
616
|
+
localIp: String,
|
|
617
|
+
error: String?,
|
|
618
|
+
) {
|
|
619
|
+
val body =
|
|
620
|
+
normalizeWifiForgetResultEvent(
|
|
621
|
+
requestId,
|
|
622
|
+
sid,
|
|
623
|
+
ssid,
|
|
624
|
+
protocolVersion,
|
|
625
|
+
outcome,
|
|
626
|
+
legacyDispatched,
|
|
627
|
+
connected,
|
|
628
|
+
currentSsid,
|
|
629
|
+
localIp,
|
|
630
|
+
error,
|
|
631
|
+
)
|
|
632
|
+
if (body == null) {
|
|
633
|
+
log("Dropping malformed wifi_forget_result without modern or legacy fields")
|
|
634
|
+
return
|
|
635
|
+
}
|
|
636
|
+
sendTypedMessage("wifi_forget_result", body)
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
@JvmStatic
|
|
640
|
+
fun sendSavedWifiNetworks(
|
|
641
|
+
requestId: String,
|
|
642
|
+
sid: String,
|
|
643
|
+
protocolVersion: Int,
|
|
644
|
+
outcome: String,
|
|
645
|
+
networks: List<String>,
|
|
646
|
+
error: String?,
|
|
647
|
+
) {
|
|
648
|
+
val body = HashMap<String, Any>()
|
|
649
|
+
body["requestId"] = requestId
|
|
650
|
+
body["sid"] = sid
|
|
651
|
+
body["protocolVersion"] = protocolVersion
|
|
652
|
+
body["outcome"] = outcome
|
|
653
|
+
body["networks"] = networks
|
|
654
|
+
if (error != null) body["error"] = error
|
|
655
|
+
sendTypedMessage("saved_wifi_networks", body)
|
|
656
|
+
}
|
|
657
|
+
|
|
582
658
|
/**
|
|
583
659
|
* Claim the WiFi scan-results store for a newly requested scan. Called by the
|
|
584
660
|
* SDK when it generates the scanId, BEFORE the scan command goes out: store
|
|
@@ -1784,6 +1784,9 @@ class DeviceManager {
|
|
|
1784
1784
|
sgc?.requestWifiScan(scanId)
|
|
1785
1785
|
}
|
|
1786
1786
|
|
|
1787
|
+
fun requestSavedWifiNetworks(requestId: String, sid: String): Boolean =
|
|
1788
|
+
sgc?.requestSavedWifiNetworks(requestId, sid) ?: false
|
|
1789
|
+
|
|
1787
1790
|
fun sendIncidentId(incidentId: String, apiBaseUrl: String? = null) {
|
|
1788
1791
|
Bridge.log("MAN: Sending incidentId to glasses for log upload: $incidentId")
|
|
1789
1792
|
sgc?.sendIncidentId(incidentId, apiBaseUrl)
|
|
@@ -1802,10 +1805,8 @@ class DeviceManager {
|
|
|
1802
1805
|
sgc?.sendWifiCredentials(ssid, password)
|
|
1803
1806
|
}
|
|
1804
1807
|
|
|
1805
|
-
fun forgetWifiNetwork(ssid: String)
|
|
1806
|
-
|
|
1807
|
-
sgc?.forgetWifiNetwork(ssid)
|
|
1808
|
-
}
|
|
1808
|
+
fun forgetWifiNetwork(ssid: String, requestId: String? = null, sid: String? = null): Boolean =
|
|
1809
|
+
sgc?.forgetWifiNetwork(ssid, requestId, sid) ?: false
|
|
1809
1810
|
|
|
1810
1811
|
fun setHotspotState(enabled: Boolean) {
|
|
1811
1812
|
Bridge.log("MAN: Setting glasses hotspot state: $enabled")
|
|
@@ -2016,9 +2017,14 @@ class DeviceManager {
|
|
|
2016
2017
|
* Request version info from glasses. Glasses will respond with version_info message containing
|
|
2017
2018
|
* build number, firmware version, etc.
|
|
2018
2019
|
*/
|
|
2019
|
-
fun requestVersionInfo() {
|
|
2020
|
+
fun requestVersionInfo(requestId: String? = null) {
|
|
2020
2021
|
Bridge.log("MAN: 📱 Requesting version info from glasses")
|
|
2021
|
-
sgc
|
|
2022
|
+
val controller = sgc
|
|
2023
|
+
if (controller is MentraLive) {
|
|
2024
|
+
controller.requestVersionInfo(requestId)
|
|
2025
|
+
} else {
|
|
2026
|
+
controller?.requestVersionInfo()
|
|
2027
|
+
}
|
|
2022
2028
|
}
|
|
2023
2029
|
|
|
2024
2030
|
/** Send shutdown command to glasses. This will initiate a graceful shutdown of the device. */
|
|
@@ -3,6 +3,6 @@ package com.mentra.bluetoothsdk
|
|
|
3
3
|
/** Generated from /changelogs. Do not edit directly. */
|
|
4
4
|
internal val GENERATED_RELEASE_CHANGELOGS: List<ReleaseChangelog> =
|
|
5
5
|
listOf(
|
|
6
|
-
ReleaseChangelog(version = "3.2.0", markdown = "This release is under active development. User-facing changes will be documented as they land
|
|
6
|
+
ReleaseChangelog(version = "3.2.0", 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
|
ReleaseChangelog(version = "3.1.0", markdown = "Software updates are more reliable, with clearer progress and recovery when the glasses restart.\n\n- MentraOS, Mentra Engine, the Bluetooth SDK, and the glasses client now share one coordinated release version.\n- Mentra Live updates can continue across APK, system, and firmware restarts without asking the user to start the same update again.\n- Bluetooth photo capture and transfer diagnostics are more precise and less disruptive to normal glasses traffic."),
|
|
8
8
|
)
|
|
@@ -3,9 +3,9 @@ package com.mentra.bluetoothsdk
|
|
|
3
3
|
/** Generated by release CI. Do not edit in a release checkout. */
|
|
4
4
|
internal object GeneratedReleaseMetadata {
|
|
5
5
|
const val FAMILY_BASE_VERSION: String = "3.2.0"
|
|
6
|
-
const val RELEASE_IDENTITY: String = "3.2.0-dev.
|
|
7
|
-
const val RELEASE_SET_ID: String = "mentra-3.2.0-dev.
|
|
8
|
-
const val SOURCE_COMMIT: String = "
|
|
9
|
-
const val OTA_MANIFEST_URL: String = "https://github.com/Mentra-Community/MentraOS/releases/download/mentra-builds-v3.2.0/mentra-live-ota-3.2.0-dev.
|
|
10
|
-
const val OTA_MANIFEST_SHA256: String = "
|
|
6
|
+
const val RELEASE_IDENTITY: String = "3.2.0-dev.203"
|
|
7
|
+
const val RELEASE_SET_ID: String = "mentra-3.2.0-dev.203"
|
|
8
|
+
const val SOURCE_COMMIT: String = "7eb4deefff27d498c2b4562d628607f399aa1f40"
|
|
9
|
+
const val OTA_MANIFEST_URL: String = "https://github.com/Mentra-Community/MentraOS/releases/download/mentra-builds-v3.2.0/mentra-live-ota-3.2.0-dev.203.json"
|
|
10
|
+
const val OTA_MANIFEST_SHA256: String = "0ae026238c8e70970be4e181cc1ae73dd905418292edebddb610012f4f0a9b6f"
|
|
11
11
|
}
|