@mentra/bluetooth-sdk 3.2.1-dev.314 → 3.3.0-beta.942
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 +72 -10
- package/android/build.gradle +15 -0
- package/android/lc3Lib/build.gradle +6 -1
- package/android/src/debug/java/com/mentra/bluetoothsdk/sgcs/NimoDiagnostics.kt +368 -0
- package/android/src/debug/java/com/mentra/bluetoothsdk/sgcs/NimoFramebufferProtocol.kt +156 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkAnalytics.kt +63 -14
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkAnalyticsTracker.kt +55 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkDefaults.kt +4 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkInstallId.kt +23 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +8 -2
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkUpdateCheck.kt +184 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkUsageTracker.kt +388 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/Bridge.kt +2 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +133 -26
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +37 -13
- package/android/src/main/java/com/mentra/bluetoothsdk/GeneratedChangelogCatalog.kt +3 -2
- package/android/src/main/java/com/mentra/bluetoothsdk/GeneratedReleaseMetadata.kt +6 -6
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +227 -36
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraLocalNetworkModule.kt +24 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/NetworkDiagnostics.kt +25 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/VersionInfoResponseAccumulator.kt +13 -2
- package/android/src/main/java/com/mentra/bluetoothsdk/debug/BleTraceLogger.kt +3 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/requests/DisplayRequests.kt +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/services/Foreground.kt +33 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/services/G2ConnectionRecovery.kt +47 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/ClassicAudioConnectionTracker.kt +4 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/ClassicAudioServiceDiscovery.kt +70 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G1.kt +2 -2
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G1TextSanitizer.kt +11 -32
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G2.kt +72 -21
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/LatinTextSanitizer.kt +39 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.kt +194 -40
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraNex.kt +12 -8
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Nimo.kt +314 -395
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/NimoCanvasCodec.kt +247 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/NimoCanvasCoordinator.kt +238 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/NimoCanvasEncoder.kt +58 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/NimoCanvasSession.kt +180 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/SGCManager.kt +11 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/SelectedDeviceAddress.kt +17 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/SerializedGattCallback.kt +5 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/G2Text.kt +7 -0
- package/android/src/release/java/com/mentra/bluetoothsdk/sgcs/NimoDiagnostics.kt +20 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/BluetoothSdkAnalyticsTrackerTest.kt +58 -3
- package/android/src/test/java/com/mentra/bluetoothsdk/BluetoothSdkUpdateCheckTest.kt +124 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/BluetoothSdkUsageTrackerTest.kt +263 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/DashboardContentFormatterTest.kt +2 -2
- package/android/src/test/java/com/mentra/bluetoothsdk/DeviceIdentityPairingTest.kt +59 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/DeviceManagerSceneHandoffTest.kt +666 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/G2TextTest.kt +13 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/NetworkDiagnosticsTest.kt +55 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/ScanCompletionTest.kt +5 -1
- package/android/src/test/java/com/mentra/bluetoothsdk/VersionInfoResponseAccumulatorTest.kt +71 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/debug/BleTraceLoggerTest.kt +69 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/services/G2ConnectionRecoveryTest.kt +51 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/ClassicAudioServiceDiscoveryTest.kt +124 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/G1PairingTest.kt +105 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/G1TextSanitizerTest.kt +29 -4
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/G2DisplayThreadTest.kt +120 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/G2PairingTargetTest.kt +90 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/MentraLiveClassicDisconnectTest.kt +133 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/MentraLiveFileTransferSessionTest.kt +55 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/MentraLiveGattCallbackTest.kt +33 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/MentraLiveIncidentDispatchTest.kt +126 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/MentraLivePairingTargetTest.kt +65 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/MentraLiveWifiCredentialLogTest.kt +55 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/MentraNexPairingTest.kt +151 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/NimoBitmapAcceptanceTest.kt +105 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/NimoCanvasCodecTest.kt +233 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/NimoCanvasCoordinatorTest.kt +348 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/NimoCanvasSessionTest.kt +339 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/NimoMicReconnectTest.kt +93 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/NimoProtocolTest.kt +11 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/SelectedDeviceAddressTest.kt +27 -0
- package/android/src/testDebug/java/com/mentra/bluetoothsdk/sgcs/NimoDiagnosticsTest.kt +287 -0
- package/android/src/testDebug/java/com/mentra/bluetoothsdk/sgcs/NimoFramebufferProtocolTest.kt +127 -0
- package/android/src/testDebug/java/com/mentra/bluetoothsdk/sgcs/NimoLifecycleTest.kt +288 -0
- package/android/src/testDebug/java/com/mentra/bluetoothsdk/sgcs/NimoReadinessDriverTest.kt +205 -0
- package/build/BluetoothSdk.types.d.ts +16 -0
- package/build/BluetoothSdk.types.d.ts.map +1 -1
- package/build/BluetoothSdk.types.js +1 -0
- package/build/BluetoothSdk.types.js.map +1 -1
- package/build/_private/BluetoothSdkModule.d.ts +4 -0
- package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
- package/build/_private/BluetoothSdkModule.js +5 -1
- package/build/_private/BluetoothSdkModule.js.map +1 -1
- package/build/_private/MentraLocalNetworkModule.d.ts +23 -0
- package/build/_private/MentraLocalNetworkModule.d.ts.map +1 -1
- package/build/_private/MentraLocalNetworkModule.js.map +1 -1
- package/build/_private/nativeLogConsole.d.ts +19 -0
- package/build/_private/nativeLogConsole.d.ts.map +1 -1
- package/build/_private/nativeLogConsole.js +12 -0
- package/build/_private/nativeLogConsole.js.map +1 -1
- package/build/generated/changelogCatalog.d.ts +5 -2
- package/build/generated/changelogCatalog.d.ts.map +1 -1
- package/build/generated/changelogCatalog.js +6 -2
- package/build/generated/changelogCatalog.js.map +1 -1
- package/build/generated/releaseMetadata.js +6 -6
- package/build/generated/releaseMetadata.js.map +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1 -0
- package/build/index.js.map +1 -1
- package/build/ota-transport/index.d.ts +4 -0
- package/build/ota-transport/index.d.ts.map +1 -1
- package/build/ota-transport/index.js +2 -0
- package/build/ota-transport/index.js.map +1 -1
- package/ios/BluetoothSdkModule.swift +17 -4
- package/ios/MentraBluetoothSDK.podspec +3 -2
- package/ios/Source/BluetoothBackgroundMode.swift +32 -0
- package/ios/Source/BluetoothSdkDefaults.swift +7 -2
- package/ios/Source/Bridge.swift +15 -2
- package/ios/Source/DeviceManager.swift +102 -66
- package/ios/Source/DeviceStore.swift +26 -7
- package/ios/Source/GeneratedChangelogCatalog.swift +3 -2
- package/ios/Source/GeneratedReleaseMetadata.swift +6 -6
- package/ios/Source/MentraBluetoothSDK.swift +313 -54
- package/ios/Source/audio/GlassesMicWatchdog.swift +33 -0
- package/ios/Source/internal/BluetoothAvailability.swift +6 -0
- package/ios/Source/internal/BluetoothSdkAnalytics.swift +87 -13
- package/ios/Source/internal/BluetoothSdkAnalyticsTracker.swift +54 -1
- package/ios/Source/internal/BluetoothSdkInstallId.swift +21 -0
- package/ios/Source/internal/BluetoothSdkUpdateCheck.swift +223 -0
- package/ios/Source/internal/BluetoothSdkUsageTracker.swift +428 -0
- package/ios/Source/requests/DisplayRequests.swift +1 -1
- package/ios/Source/requests/VersionInfoResponseAccumulator.swift +11 -1
- package/ios/Source/sgcs/Ar99.swift +1 -2
- package/ios/Source/sgcs/G1.swift +68 -38
- package/ios/Source/sgcs/G1ConnectionTarget.swift +13 -0
- package/ios/Source/sgcs/G2.swift +261 -247
- package/ios/Source/sgcs/G2ConnectionSession.swift +101 -0
- package/ios/Source/sgcs/Mach1.swift +19 -7
- package/ios/Source/sgcs/MentraLive.swift +272 -197
- package/ios/Source/sgcs/MentraLiveFileTransferSession.swift +137 -0
- package/ios/Source/sgcs/MentraLivePairingAdvertisement.swift +47 -0
- package/ios/Source/sgcs/MentraNex.swift +8 -31
- package/ios/Source/sgcs/NexConnectionTarget.swift +15 -0
- package/ios/Source/sgcs/Nimo.swift +280 -408
- package/ios/Source/sgcs/NimoCanvasCodec.swift +272 -0
- package/ios/Source/sgcs/NimoCanvasCoordinator.swift +233 -0
- package/ios/Source/sgcs/NimoCanvasImage.swift +53 -0
- package/ios/Source/sgcs/NimoCanvasSession.swift +166 -0
- package/ios/Source/sgcs/NimoDiscovery.swift +49 -0
- package/ios/Source/sgcs/SGCManager.swift +25 -1
- package/ios/Source/sgcs/VuzixConnectionTarget.swift +15 -0
- package/ios/Source/status/DeviceStatus.swift +1 -0
- package/ios/Source/stt/SherpaOnnxTranscriber.swift +49 -189
- package/ios/Source/utils/AudioSessionMonitor.swift +44 -8
- package/ios/Source/utils/DemandDrivenAudioWorker.swift +150 -0
- package/ios/Source/utils/G1Text.swift +2 -24
- package/ios/Source/utils/G1TextSanitizer.swift +27 -0
- package/ios/Source/utils/G2Text.swift +11 -0
- package/ios/Source/utils/LatinTextSanitizer.swift +48 -0
- package/ios/Tests/BluetoothAudioRouteTests.swift +26 -0
- package/ios/Tests/BluetoothBackgroundModeTests.swift +32 -0
- package/ios/Tests/BluetoothSdkAnalyticsTrackerTests.swift +54 -3
- package/ios/Tests/BluetoothSdkUpdateCheckTests.swift +153 -0
- package/ios/Tests/BluetoothSdkUsageTrackerTests.swift +296 -0
- package/ios/Tests/DashboardContentFormatterTests.swift +2 -2
- package/ios/Tests/DashboardRenderingTests.swift +31 -0
- package/ios/Tests/DemandDrivenAudioWorkerTests.swift +157 -0
- package/ios/Tests/DeviceIdentityPairingTests.swift +49 -0
- package/ios/Tests/DeviceManagerReconnectTests.swift +362 -0
- package/ios/Tests/G1PairingTests.swift +162 -0
- package/ios/Tests/G1TextSanitizerTests.swift +32 -0
- package/ios/Tests/G2ConnectionSessionTests.swift +172 -0
- package/ios/Tests/G2TextTests.swift +10 -0
- package/ios/Tests/IncidentLogDispatchTests.swift +111 -0
- package/ios/Tests/MentraLiveFileTransferSessionTests.swift +47 -0
- package/ios/Tests/MentraLiveReconnectPolicyTests.swift +58 -0
- package/ios/Tests/MentraLiveSessionReadinessTests.swift +163 -0
- package/ios/Tests/NimoCanvasCodecTests.swift +217 -0
- package/ios/Tests/NimoCanvasSessionTests.swift +181 -0
- package/ios/Tests/NimoCanvasTransportTests.swift +186 -0
- package/ios/Tests/NimoDeviceCommandsTests.swift +16 -0
- package/ios/Tests/NimoDiscoveryTests.swift +79 -0
- package/ios/Tests/SGCConnectionTargetTests.swift +48 -0
- package/ios/Tests/VersionInfoCommandTests.swift +194 -0
- package/ios/Tests/VersionInfoResponseAccumulatorTests.swift +65 -0
- package/ios/Tests/VideoRecordingCommandTests.swift +81 -0
- package/ios/Tests/WifiCredentialLoggingTests.swift +42 -0
- package/package.json +2 -2
- package/plugin/build/withIos.d.ts +2 -0
- package/plugin/build/withIos.js +12 -1
- package/src/BluetoothSdk.types.ts +15 -0
- package/src/_private/BluetoothSdkModule.ts +10 -1
- package/src/_private/MentraLocalNetworkModule.ts +28 -0
- package/src/_private/nativeLogConsole.ts +19 -0
- package/src/generated/changelogCatalog.ts +6 -2
- package/src/generated/releaseMetadata.ts +6 -6
- package/src/index.ts +1 -0
- package/src/ota-transport/index.ts +10 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ota-transport/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ota-transport/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -11,6 +11,8 @@ import MentraOtaServer from "../ota-server";
|
|
|
11
11
|
*/
|
|
12
12
|
export const otaLocalNetwork = Object.freeze({
|
|
13
13
|
isAvailable: () => MentraLocalNetwork != null,
|
|
14
|
+
/** Synchronous local observation; no network I/O, route changes or network identities. */
|
|
15
|
+
getNetworkDiagnostics: () => MentraLocalNetwork?.getNetworkDiagnostics() ?? null,
|
|
14
16
|
connect: async (ssid, password) => {
|
|
15
17
|
if (!MentraLocalNetwork)
|
|
16
18
|
throw new Error("Scoped local networking is not available in this native build");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/ota-transport/index.ts"],"names":[],"mappings":"AAAA,oFAAoF;AACpF,iDAAiD;AACjD,OAAO,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/ota-transport/index.ts"],"names":[],"mappings":"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","sourcesContent":["// This public adapter is the package-owned boundary over its native implementation.\n// eslint-disable-next-line no-restricted-imports\nimport MentraLocalNetwork, {type NetworkDiagnostics} from \"../_private/MentraLocalNetworkModule\"\n// eslint-disable-next-line no-restricted-imports\nimport MentraOtaServer from \"../ota-server\"\n\n// eslint-disable-next-line no-restricted-imports\nexport type {\n NetworkCapabilitiesDiagnostic,\n NetworkDiagnostics,\n NetworkObservation,\n} from \"../_private/MentraLocalNetworkModule\"\n\nexport type OtaTransportSubscription = {remove(): void}\n\nexport type OtaLocalNetworkConnection = {\n connected: boolean\n ssid: string\n localAddress?: string\n}\n\nexport type OtaLocalNetworkLostEvent = {ssid: string}\n\nexport type OtaLocalNetworkResponse = {\n status: number\n headers: Record<string, string>\n bodyBase64: string\n}\n\nexport type OtaLocalNetworkDownloadResult = {\n statusCode: number\n bytesWritten: number\n headers: Record<string, string>\n}\n\nexport type OtaLocalNetworkDownloadProgress = {\n requestId: string\n bytesWritten: number\n contentLength: number\n statusCode?: number\n headers?: Record<string, string>\n}\n\nexport type OtaArtifactDownloadProgress = {\n destination: string\n bytesWritten: number\n contentLength: number\n}\n\nexport type OtaServerResult = {\n baseUrl: string\n host: string\n manifestUrl: string\n port: number\n}\n\n/**\n * Scoped networking for traffic that must stay on a Mentra Live hotspot while\n * the phone's default route remains on cellular or another Wi-Fi network.\n *\n * The module is optional because iOS uses its system Wi-Fi join flow instead.\n */\nexport const otaLocalNetwork = Object.freeze({\n isAvailable: (): boolean => MentraLocalNetwork != null,\n /** Synchronous local observation; no network I/O, route changes or network identities. */\n getNetworkDiagnostics: (): NetworkDiagnostics | null => MentraLocalNetwork?.getNetworkDiagnostics() ?? null,\n connect: async (ssid: string, password: string): Promise<OtaLocalNetworkConnection> => {\n if (!MentraLocalNetwork) throw new Error(\"Scoped local networking is not available in this native build\")\n return MentraLocalNetwork.connect(ssid, password)\n },\n request: async (\n requestId: string,\n url: string,\n method: string,\n headers: Record<string, string>,\n body: string | null,\n timeoutMs: number,\n ): Promise<OtaLocalNetworkResponse> => {\n if (!MentraLocalNetwork) throw new Error(\"Scoped local networking is not available in this native build\")\n return MentraLocalNetwork.request(requestId, url, method, headers, body, timeoutMs)\n },\n download: async (\n requestId: string,\n url: string,\n destination: string,\n headers: Record<string, string>,\n connectionTimeoutMs: number,\n readTimeoutMs: number,\n ): Promise<OtaLocalNetworkDownloadResult> => {\n if (!MentraLocalNetwork) throw new Error(\"Scoped local networking is not available in this native build\")\n return MentraLocalNetwork.download(requestId, url, destination, headers, connectionTimeoutMs, readTimeoutMs)\n },\n cancel: async (requestId: string): Promise<void> => {\n if (!MentraLocalNetwork) return\n await MentraLocalNetwork.cancel(requestId)\n },\n disconnect: async (): Promise<void> => {\n if (!MentraLocalNetwork) return\n await MentraLocalNetwork.disconnect()\n },\n onDownloadProgress: (listener: (event: OtaLocalNetworkDownloadProgress) => void): OtaTransportSubscription => {\n if (!MentraLocalNetwork) return {remove() {}}\n return MentraLocalNetwork.addListener(\"downloadProgress\", listener)\n },\n onNetworkLost: (listener: (event: OtaLocalNetworkLostEvent) => void): OtaTransportSubscription => {\n if (!MentraLocalNetwork) return {remove() {}}\n return MentraLocalNetwork.addListener(\"networkLost\", listener)\n },\n})\n\n/** Native HTTP server and artifact downloader used by the Engine OTA coordinator. */\nexport const otaServer = Object.freeze({\n start: (\n manifestJson: string,\n artifactPaths: Record<string, string>,\n host?: string | null,\n ): Promise<OtaServerResult> => MentraOtaServer.startOtaServer(manifestJson, artifactPaths, host),\n stop: (): Promise<void> => MentraOtaServer.stopOtaServer(),\n waitForWifiAddress: (gateway: string, timeoutMs: number): Promise<string> =>\n MentraOtaServer.waitForWifiAddress(gateway, timeoutMs),\n downloadArtifact: (source: string, destination: string): Promise<{statusCode: number; bytesWritten: number}> =>\n MentraOtaServer.downloadArtifact(source, destination),\n onArtifactDownloadProgress: (listener: (event: OtaArtifactDownloadProgress) => void): OtaTransportSubscription =>\n MentraOtaServer.addListener(\"artifactDownloadProgress\", listener),\n})\n"]}
|
|
@@ -12,6 +12,10 @@ private func codedBridgeError(_ error: BluetoothSdkError) -> Exception {
|
|
|
12
12
|
Exception(name: error.code, description: error.message, code: error.code)
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
/// Offline model archives take tens of seconds to extract. Android runs this on Dispatchers.IO;
|
|
16
|
+
/// on Expo's shared default queue it would hold every other module's AsyncFunction behind it.
|
|
17
|
+
private let modelExtractionQueue = DispatchQueue(label: "com.mentra.model-extraction", qos: .utility)
|
|
18
|
+
|
|
15
19
|
public class BluetoothSdkModule: Module, MentraBluetoothSDKDelegate {
|
|
16
20
|
private var sdk: MentraBluetoothSDK?
|
|
17
21
|
|
|
@@ -23,6 +27,7 @@ public class BluetoothSdkModule: Module, MentraBluetoothSDKDelegate {
|
|
|
23
27
|
"glasses_status",
|
|
24
28
|
"bluetooth_status",
|
|
25
29
|
"log",
|
|
30
|
+
"sdk_update_notice",
|
|
26
31
|
"device_discovered",
|
|
27
32
|
"default_device_changed",
|
|
28
33
|
// Individual event handlers
|
|
@@ -90,7 +95,6 @@ public class BluetoothSdkModule: Module, MentraBluetoothSDKDelegate {
|
|
|
90
95
|
"send_command_to_ble",
|
|
91
96
|
"receive_command_from_ble",
|
|
92
97
|
"miniapp_selected",
|
|
93
|
-
"captions_tester_incident",
|
|
94
98
|
"extraction_progress"
|
|
95
99
|
)
|
|
96
100
|
|
|
@@ -226,6 +230,10 @@ public class BluetoothSdkModule: Module, MentraBluetoothSDKDelegate {
|
|
|
226
230
|
}
|
|
227
231
|
}
|
|
228
232
|
|
|
233
|
+
AsyncFunction("unpair") {
|
|
234
|
+
try await DeviceManager.shared.unpair()
|
|
235
|
+
}
|
|
236
|
+
|
|
229
237
|
AsyncFunction("forgetController") {
|
|
230
238
|
await MainActor.run {
|
|
231
239
|
DeviceManager.shared.forgetController()
|
|
@@ -361,8 +369,8 @@ public class BluetoothSdkModule: Module, MentraBluetoothSDKDelegate {
|
|
|
361
369
|
// MARK: - Incident Reporting
|
|
362
370
|
|
|
363
371
|
AsyncFunction("sendIncidentId") { (incidentId: String, apiBaseUrl: String?) in
|
|
364
|
-
await MainActor.run {
|
|
365
|
-
self.bluetoothSdk().sendIncidentId(incidentId, apiBaseUrl: apiBaseUrl)
|
|
372
|
+
try await MainActor.run {
|
|
373
|
+
try self.bluetoothSdk().sendIncidentId(incidentId, apiBaseUrl: apiBaseUrl)
|
|
366
374
|
}
|
|
367
375
|
}
|
|
368
376
|
|
|
@@ -465,6 +473,11 @@ public class BluetoothSdkModule: Module, MentraBluetoothSDKDelegate {
|
|
|
465
473
|
try await sdk.setLoudnessGateEnabled(enabled)
|
|
466
474
|
}
|
|
467
475
|
|
|
476
|
+
AsyncFunction("setAutoPowerOffEnabled") { (enabled: Bool) in
|
|
477
|
+
let sdk = await MainActor.run { self.bluetoothSdk() }
|
|
478
|
+
try await sdk.setAutoPowerOffEnabled(enabled)
|
|
479
|
+
}
|
|
480
|
+
|
|
468
481
|
AsyncFunction("setPhotoCaptureDefaults") { (params: [String: Any]) in
|
|
469
482
|
let sdk = await MainActor.run { self.bluetoothSdk() }
|
|
470
483
|
return try await sdk.setPhotoCaptureDefaults(PhotoCaptureDefaults.from(params: params)).values
|
|
@@ -836,7 +849,7 @@ public class BluetoothSdkModule: Module, MentraBluetoothSDKDelegate {
|
|
|
836
849
|
|
|
837
850
|
AsyncFunction("extractTarBz2") { (sourcePath: String, destinationPath: String) -> Bool in
|
|
838
851
|
return STTTools.extractTarBz2(sourcePath: sourcePath, destinationPath: destinationPath)
|
|
839
|
-
}
|
|
852
|
+
}.runOnQueue(modelExtractionQueue)
|
|
840
853
|
|
|
841
854
|
// MARK: - TTS Model Management
|
|
842
855
|
|
|
@@ -38,13 +38,14 @@ Pod::Spec.new do |s|
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
# iOS frameworks required by Bluetooth SDK
|
|
41
|
-
ios_frameworks = ['AVFoundation', 'CoreBluetooth', 'UIKit', 'CoreGraphics']
|
|
41
|
+
ios_frameworks = ['AVFoundation', 'CoreBluetooth', 'UIKit', 'CoreGraphics', 'ImageIO']
|
|
42
42
|
ios_frameworks << 'Network' if include_expo_adapter
|
|
43
43
|
s.ios.frameworks = ios_frameworks
|
|
44
44
|
s.osx.frameworks = ['AVFoundation', 'CoreBluetooth', 'CoreAudio', 'AudioToolbox', 'ImageIO', 'JavaScriptCore', 'Network']
|
|
45
45
|
|
|
46
46
|
# System libraries required by MentraOS
|
|
47
|
-
s.ios.
|
|
47
|
+
s.ios.libraries = 'bz2', 'z'
|
|
48
|
+
s.osx.library = 'z'
|
|
48
49
|
|
|
49
50
|
# Vendored frameworks
|
|
50
51
|
s.ios.vendored_frameworks = 'Packages/SherpaOnnx/sherpa-onnx.xcframework'
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
/// Whether the host app lets iOS keep the glasses link working while the app is suspended.
|
|
4
|
+
///
|
|
5
|
+
/// The SDK keeps a pending reconnection open after the glasses drop the link (for example while
|
|
6
|
+
/// their Bluetooth chip restarts during a software update). iOS completes it, and delivers BLE
|
|
7
|
+
/// events, only to apps that declare the `bluetooth-central` background mode. A Swift package
|
|
8
|
+
/// cannot declare it for the app, so the SDK checks it and warns instead.
|
|
9
|
+
enum BluetoothBackgroundMode {
|
|
10
|
+
static let requiredMode = "bluetooth-central"
|
|
11
|
+
|
|
12
|
+
/// Platform-independent plist check, so tests exercise it on every host.
|
|
13
|
+
static func declaresBluetoothCentral(_ infoDictionary: [String: Any]?) -> Bool {
|
|
14
|
+
guard let modes = infoDictionary?["UIBackgroundModes"] as? [String] else { return false }
|
|
15
|
+
return modes.contains(requiredMode)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
static func isDeclared(in infoDictionary: [String: Any]?) -> Bool {
|
|
19
|
+
#if os(iOS)
|
|
20
|
+
return declaresBluetoothCentral(infoDictionary)
|
|
21
|
+
#else
|
|
22
|
+
// macOS apps are not suspended the same way and have no background modes.
|
|
23
|
+
return true
|
|
24
|
+
#endif
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
static let missingWarning =
|
|
28
|
+
"MentraBluetoothSDK: WARNING Info.plist is missing UIBackgroundModes `bluetooth-central`. "
|
|
29
|
+
+ "While the phone is locked or the app is in the background, iOS will not reconnect to "
|
|
30
|
+
+ "the glasses after a link drop (for example during a software update) until the app "
|
|
31
|
+
+ "is opened. See https://docs.mentraglass.com/bluetooth-sdk/ios#background-operation"
|
|
32
|
+
}
|
|
@@ -9,9 +9,14 @@ enum BluetoothSdkDefaults {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
static let voiceActivityDetectionEnabled = false
|
|
12
|
-
static let loudnessGateEnabled =
|
|
12
|
+
static let loudnessGateEnabled = true
|
|
13
|
+
|
|
14
|
+
/// Must stay true. The firmware defaults auto power-off on, and we re-push
|
|
15
|
+
/// every Bluetooth setting on connect, so a false default here would
|
|
16
|
+
/// silently disable it on every pair the app touches.
|
|
17
|
+
static let autoPowerOffEnabled = false
|
|
13
18
|
private static let infoSdkVersionKey = "MentraBluetoothSdkVersion"
|
|
14
|
-
private static let swiftPackageSdkVersion = "3.
|
|
19
|
+
private static let swiftPackageSdkVersion = "3.3.0-beta.942"
|
|
15
20
|
private static let swiftPackageSdkVersionPlaceholder = "__MENTRA" + "_BLUETOOTH_SDK_VERSION__"
|
|
16
21
|
|
|
17
22
|
private static func normalizedSdkVersion(_ value: String?) -> String? {
|
package/ios/Source/Bridge.swift
CHANGED
|
@@ -335,8 +335,21 @@ class Bridge {
|
|
|
335
335
|
}
|
|
336
336
|
|
|
337
337
|
/// Wear state from sr_wrst, or from a wear transition on the glasses.
|
|
338
|
-
static func sendWearState(
|
|
339
|
-
|
|
338
|
+
static func sendWearState(
|
|
339
|
+
worn: Bool,
|
|
340
|
+
elapsedMs: Int? = nil,
|
|
341
|
+
timeoutMs: Int? = nil,
|
|
342
|
+
enabled: Bool? = nil,
|
|
343
|
+
armed: Bool? = nil,
|
|
344
|
+
inhibited: Bool? = nil
|
|
345
|
+
) {
|
|
346
|
+
var body: [String: Any] = ["worn": worn]
|
|
347
|
+
if let elapsedMs { body["elapsedMs"] = elapsedMs }
|
|
348
|
+
if let timeoutMs { body["timeoutMs"] = timeoutMs }
|
|
349
|
+
if let enabled { body["enabled"] = enabled }
|
|
350
|
+
if let armed { body["armed"] = armed }
|
|
351
|
+
if let inhibited { body["inhibited"] = inhibited }
|
|
352
|
+
Bridge.sendTypedMessage("wear_state", body: body)
|
|
340
353
|
}
|
|
341
354
|
|
|
342
355
|
/// Wear vote config the glasses report as in force, from sr_weartun.
|
|
@@ -318,9 +318,9 @@ struct ViewState {
|
|
|
318
318
|
var lc3Converter: PcmConverter?
|
|
319
319
|
/// Audio output format - defaults to LC3 for bandwidth savings
|
|
320
320
|
private var audioOutputFormat: AudioOutputFormat = .lc3
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
321
|
+
private var micWatchdog = GlassesMicWatchdog()
|
|
322
|
+
// Injectable monotonic clock so recovery deadlines can be tested without sleeping.
|
|
323
|
+
var micWatchdogNow: () -> TimeInterval = { ProcessInfo.processInfo.systemUptime }
|
|
324
324
|
private var sequenceGapEvents: Int64 = 0
|
|
325
325
|
private var decodeFailures: Int64 = 0
|
|
326
326
|
private var lastLc3ReceivedAt: Int64?
|
|
@@ -330,7 +330,7 @@ struct ViewState {
|
|
|
330
330
|
|
|
331
331
|
/// STT:
|
|
332
332
|
#if !os(macOS) && (!SWIFT_PACKAGE || MENTRA_FEATURE_LOCAL_STT)
|
|
333
|
-
private
|
|
333
|
+
private let transcriber = SherpaOnnxTranscriber()
|
|
334
334
|
#endif
|
|
335
335
|
|
|
336
336
|
var viewStates: [ViewState] = [
|
|
@@ -383,24 +383,6 @@ struct ViewState {
|
|
|
383
383
|
// Start memory monitoring (logs every 30s to help detect leaks)
|
|
384
384
|
// MemoryMonitor.start()
|
|
385
385
|
|
|
386
|
-
// Initialize SherpaOnnx Transcriber
|
|
387
|
-
#if !os(macOS) && (!SWIFT_PACKAGE || MENTRA_FEATURE_LOCAL_STT)
|
|
388
|
-
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
|
|
389
|
-
let window = windowScene.windows.first,
|
|
390
|
-
let rootViewController = window.rootViewController
|
|
391
|
-
{
|
|
392
|
-
transcriber = SherpaOnnxTranscriber(context: rootViewController)
|
|
393
|
-
} else {
|
|
394
|
-
Bridge.log("Failed to create SherpaOnnxTranscriber - no root view controller found")
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
// Initialize the transcriber
|
|
398
|
-
if let transcriber = transcriber {
|
|
399
|
-
transcriber.initialize()
|
|
400
|
-
Bridge.log("SherpaOnnxTranscriber fully initialized")
|
|
401
|
-
}
|
|
402
|
-
#endif
|
|
403
|
-
|
|
404
386
|
// Initialize persistent LC3 converter for unified audio encoding
|
|
405
387
|
lc3Converter = PcmConverter()
|
|
406
388
|
Bridge.log("LC3 converter initialized for unified audio encoding")
|
|
@@ -472,10 +454,17 @@ struct ViewState {
|
|
|
472
454
|
}
|
|
473
455
|
|
|
474
456
|
func reportGlassesAudioActivity() {
|
|
475
|
-
|
|
457
|
+
micWatchdog.receivedAudio(at: micWatchdogNow())
|
|
476
458
|
lastPcmProducedAt = nowMs()
|
|
477
459
|
}
|
|
478
460
|
|
|
461
|
+
/// Decoded glasses audio must update recovery health; phone PCM must not.
|
|
462
|
+
func handleGlassesPcm(_ pcmData: Data) {
|
|
463
|
+
guard !pcmData.isEmpty else { return }
|
|
464
|
+
reportGlassesAudioActivity()
|
|
465
|
+
handlePcm(pcmData)
|
|
466
|
+
}
|
|
467
|
+
|
|
479
468
|
private func micHealth() -> MicHealth {
|
|
480
469
|
MicHealth(
|
|
481
470
|
sequenceGapEvents: sequenceGapEvents,
|
|
@@ -499,13 +488,13 @@ struct ViewState {
|
|
|
499
488
|
lastLc3ReceivedAt = nil
|
|
500
489
|
lastPcmProducedAt = nil
|
|
501
490
|
lastLc3Sequence = nil
|
|
502
|
-
|
|
491
|
+
micWatchdog = GlassesMicWatchdog()
|
|
503
492
|
}
|
|
504
493
|
|
|
505
494
|
private func recordLc3Packet(sequenceNumber: Int?) {
|
|
506
495
|
let receivedAt = nowMs()
|
|
507
496
|
lastLc3ReceivedAt = receivedAt
|
|
508
|
-
|
|
497
|
+
micWatchdog.receivedAudio(at: micWatchdogNow())
|
|
509
498
|
var hasGap = false
|
|
510
499
|
if let sequenceNumber {
|
|
511
500
|
let normalized = sequenceNumber & 0xFF
|
|
@@ -535,7 +524,7 @@ struct ViewState {
|
|
|
535
524
|
// Send PCM to local transcriber.
|
|
536
525
|
#if !os(macOS) && (!SWIFT_PACKAGE || MENTRA_FEATURE_LOCAL_STT)
|
|
537
526
|
if shouldSendTranscript || localSttFallbackActive {
|
|
538
|
-
transcriber
|
|
527
|
+
transcriber.acceptAudio(pcm16le: pcmData)
|
|
539
528
|
}
|
|
540
529
|
#endif
|
|
541
530
|
}
|
|
@@ -605,6 +594,7 @@ struct ViewState {
|
|
|
605
594
|
}
|
|
606
595
|
|
|
607
596
|
currentMic = micUsed
|
|
597
|
+
updateGlassesMicExpectation()
|
|
608
598
|
|
|
609
599
|
// log if no mic was found:
|
|
610
600
|
if micUsed == "" && micEnabled {
|
|
@@ -723,6 +713,7 @@ struct ViewState {
|
|
|
723
713
|
Bridge.log("MAN: Manager already initialized, cleaning up previous sgc")
|
|
724
714
|
sgc?.cleanup()
|
|
725
715
|
sgc = nil
|
|
716
|
+
DeviceStore.shared.apply("glasses", "micEnabled", false)
|
|
726
717
|
resetSystemTimeSync()
|
|
727
718
|
}
|
|
728
719
|
|
|
@@ -902,27 +893,20 @@ struct ViewState {
|
|
|
902
893
|
return result
|
|
903
894
|
}
|
|
904
895
|
|
|
905
|
-
private func
|
|
906
|
-
//
|
|
907
|
-
|
|
908
|
-
let
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
Bridge.log("MAN: Glasses mic intentionally suspended for phone audio; skipping mic recovery")
|
|
915
|
-
return
|
|
916
|
-
}
|
|
917
|
-
|
|
918
|
-
if PhoneAudioMonitor.getInstance().isOwnAppAudioPlaying() {
|
|
919
|
-
Bridge.log("MAN: Mentra audio is playing; skipping glasses mic recovery")
|
|
920
|
-
return
|
|
921
|
-
}
|
|
896
|
+
private func updateGlassesMicExpectation() {
|
|
897
|
+
// Demand/route and device readiness decide whether audio is expected. The
|
|
898
|
+
// shared micEnabled flag alone is not evidence of a running hardware stream.
|
|
899
|
+
let expected = micEnabled && currentMic == MicTypes.GLASSES_CUSTOM
|
|
900
|
+
&& sgc?.fullyBooted == true && sgc?.hasMic == true
|
|
901
|
+
&& sgc?.isMicSuspendedForAudio != true
|
|
902
|
+
&& !PhoneAudioMonitor.getInstance().isOwnAppAudioPlaying()
|
|
903
|
+
micWatchdog.expectAudio(expected, at: micWatchdogNow())
|
|
904
|
+
}
|
|
922
905
|
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
906
|
+
func checkAndReinitGlassesMic() {
|
|
907
|
+
updateGlassesMicExpectation()
|
|
908
|
+
if micWatchdog.shouldRetry(at: micWatchdogNow()) {
|
|
909
|
+
Bridge.log("MAN: Expected glasses audio missing for 5 seconds; retrying mic start")
|
|
926
910
|
sgc?.setMicEnabled(true)
|
|
927
911
|
}
|
|
928
912
|
}
|
|
@@ -982,7 +966,7 @@ struct ViewState {
|
|
|
982
966
|
func restartTranscriber() {
|
|
983
967
|
#if !os(macOS) && (!SWIFT_PACKAGE || MENTRA_FEATURE_LOCAL_STT)
|
|
984
968
|
Bridge.log("MAN: Restarting SherpaOnnxTranscriber via command")
|
|
985
|
-
transcriber
|
|
969
|
+
transcriber.restart()
|
|
986
970
|
#else
|
|
987
971
|
Bridge.log("MAN: Local STT is not included in this SwiftPM build")
|
|
988
972
|
#endif
|
|
@@ -998,12 +982,16 @@ struct ViewState {
|
|
|
998
982
|
Bridge.log("MAN: handleDeviceReady(): \(sgc.type)")
|
|
999
983
|
resetMicHealth()
|
|
1000
984
|
|
|
985
|
+
// A new identity must never inherit the previous glasses' address when
|
|
986
|
+
// pairing supplied only a name. Same-device reconnects retain their cache.
|
|
987
|
+
let identityChanged = !pendingDeviceName.isEmpty &&
|
|
988
|
+
(pendingDeviceName != deviceName || sgc.type != defaultWearable)
|
|
989
|
+
if identityChanged || !pendingDeviceAddress.isEmpty {
|
|
990
|
+
deviceAddress = pendingDeviceAddress
|
|
991
|
+
}
|
|
1001
992
|
if !pendingDeviceName.isEmpty {
|
|
1002
993
|
deviceName = pendingDeviceName
|
|
1003
994
|
}
|
|
1004
|
-
if !pendingDeviceAddress.isEmpty {
|
|
1005
|
-
deviceAddress = pendingDeviceAddress
|
|
1006
|
-
}
|
|
1007
995
|
clearPendingConnection()
|
|
1008
996
|
defaultWearable = sgc.type
|
|
1009
997
|
searching = false
|
|
@@ -1020,18 +1008,22 @@ struct ViewState {
|
|
|
1020
1008
|
let h = DeviceStore.shared.get("bluetooth", "dashboard_height") as? Int ?? 4
|
|
1021
1009
|
// Fall back to the canonical default (2), matching DeviceStore, not 1.
|
|
1022
1010
|
let rawDepth = DeviceStore.shared.get("bluetooth", "dashboard_depth") as? Int ?? 2
|
|
1023
|
-
let d = min(max(rawDepth, 1), 4)
|
|
1011
|
+
let d = sgc.type == DeviceTypes.NIMO ? min(max(rawDepth, 0), 10) : min(max(rawDepth, 1), 4)
|
|
1024
1012
|
sgc.setDashboardPosition(h, d)
|
|
1025
1013
|
}
|
|
1026
1014
|
|
|
1027
|
-
//
|
|
1015
|
+
// Preserve the connected-edge scene replay on full-frame adapters.
|
|
1028
1016
|
if shouldSendBootingMessage {
|
|
1029
|
-
Task {
|
|
1030
|
-
await sgc.sendTextWall("// MentraOS Connected")
|
|
1031
|
-
try? await Task.sleep(nanoseconds: 3_000_000_000) // 1 second
|
|
1032
|
-
sgc.clearDisplay()
|
|
1033
|
-
}
|
|
1034
1017
|
shouldSendBootingMessage = false
|
|
1018
|
+
if sgc.showConnectionConfirmation {
|
|
1019
|
+
Task {
|
|
1020
|
+
guard (self.sgc as AnyObject?) === (sgc as AnyObject) else { return }
|
|
1021
|
+
await sgc.sendTextWall("// MentraOS Connected")
|
|
1022
|
+
try? await Task.sleep(nanoseconds: 3_000_000_000)
|
|
1023
|
+
guard (self.sgc as AnyObject?) === (sgc as AnyObject) else { return }
|
|
1024
|
+
sgc.clearDisplay()
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1035
1027
|
}
|
|
1036
1028
|
|
|
1037
1029
|
// Call device-specific setup handlers
|
|
@@ -1058,6 +1050,11 @@ struct ViewState {
|
|
|
1058
1050
|
#endif
|
|
1059
1051
|
checkCurrentAudioDevice()
|
|
1060
1052
|
|
|
1053
|
+
// Disconnect clears micEnabled but preserves the consumers' audio requests.
|
|
1054
|
+
// Recompute demand before selecting a microphone; unchanged requests are
|
|
1055
|
+
// deduplicated by DeviceStore.apply().
|
|
1056
|
+
setMicState()
|
|
1057
|
+
|
|
1061
1058
|
// save the default_wearable now that we're connected:
|
|
1062
1059
|
Bridge.saveSetting("default_wearable", defaultWearable)
|
|
1063
1060
|
Bridge.saveSetting("device_name", deviceName)
|
|
@@ -1214,7 +1211,9 @@ struct ViewState {
|
|
|
1214
1211
|
self.dashboardSceneCleanupPending = false
|
|
1215
1212
|
let elementIds = Array(self.pendingDashboardSceneElementIds)
|
|
1216
1213
|
self.pendingDashboardSceneElementIds.removeAll()
|
|
1217
|
-
|
|
1214
|
+
if self.sgc?.sceneHandoffRequiresClear == true {
|
|
1215
|
+
await self.sgc?.clearSceneElements(elementIds)
|
|
1216
|
+
}
|
|
1218
1217
|
}
|
|
1219
1218
|
self.dashboardSceneCleanupTask = nil
|
|
1220
1219
|
}
|
|
@@ -1261,7 +1260,7 @@ struct ViewState {
|
|
|
1261
1260
|
if stateIndex == 1, dashboardSceneCleanupDeferred {
|
|
1262
1261
|
dashboardSceneCleanupPending = true
|
|
1263
1262
|
pendingDashboardSceneElementIds.formUnion(prevFrame.elements.map(\.id))
|
|
1264
|
-
} else if layoutType != "clear_view" {
|
|
1263
|
+
} else if layoutType != "clear_view", shouldClearSceneHandoff(stateIndex) {
|
|
1265
1264
|
let ids = prevFrame.elements.map(\.id)
|
|
1266
1265
|
Task { [weak self] in
|
|
1267
1266
|
await self?.sgc?.clearSceneElements(ids)
|
|
@@ -1355,7 +1354,7 @@ struct ViewState {
|
|
|
1355
1354
|
// on G2 - no page rebuild).
|
|
1356
1355
|
let prevLegacyType = viewStates[stateIndex].layoutType
|
|
1357
1356
|
let cleanupDeferred = stateIndex == 1 && dashboardSceneCleanupDeferred
|
|
1358
|
-
if !cleanupDeferred,
|
|
1357
|
+
if !cleanupDeferred, shouldClearSceneHandoff(stateIndex),
|
|
1359
1358
|
!prevLegacyType.isEmpty,
|
|
1360
1359
|
prevLegacyType != "clear_view",
|
|
1361
1360
|
prevLegacyType != "scene"
|
|
@@ -1371,7 +1370,7 @@ struct ViewState {
|
|
|
1371
1370
|
if stateIndex == 1, dashboardSceneCleanupDeferred {
|
|
1372
1371
|
dashboardSceneCleanupPending = true
|
|
1373
1372
|
pendingDashboardSceneElementIds.formUnion(prevFrame.elements.map(\.id))
|
|
1374
|
-
} else {
|
|
1373
|
+
} else if shouldClearSceneHandoff(stateIndex) {
|
|
1375
1374
|
let ids = prevFrame.elements.map(\.id)
|
|
1376
1375
|
Task { [weak self] in
|
|
1377
1376
|
await self?.sgc?.clearSceneElements(ids)
|
|
@@ -1395,6 +1394,13 @@ struct ViewState {
|
|
|
1395
1394
|
}
|
|
1396
1395
|
}
|
|
1397
1396
|
|
|
1397
|
+
private func shouldClearSceneHandoff(_ stateIndex: Int) -> Bool {
|
|
1398
|
+
let visibleIndex = headUp && contextualDashboard ? 1 : 0
|
|
1399
|
+
guard let sgc else { return false }
|
|
1400
|
+
return stateIndex == visibleIndex && !screenDisabled && sgc.fullyBooted
|
|
1401
|
+
&& !sgc.type.contains(DeviceTypes.SIMULATED) && sgc.sceneHandoffRequiresClear
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1398
1404
|
/// Guarded scene dispatch - mirrors sendCurrentState's send conditions.
|
|
1399
1405
|
private func dispatchSceneFrame(_ frame: SceneFrame, stateIndex: Int) {
|
|
1400
1406
|
if screenDisabled { return }
|
|
@@ -1512,13 +1518,16 @@ struct ViewState {
|
|
|
1512
1518
|
sgc?.requestSavedWifiNetworks(requestId: requestId, sid: sid) ?? false
|
|
1513
1519
|
}
|
|
1514
1520
|
|
|
1515
|
-
func sendIncidentId(_ incidentId: String, apiBaseUrl: String? = nil) {
|
|
1521
|
+
func sendIncidentId(_ incidentId: String, apiBaseUrl: String? = nil) throws {
|
|
1522
|
+
guard let driver = sgc else {
|
|
1523
|
+
throw BluetoothSdkError(code: "glasses_not_connected", message: "Cannot request incident logs because glasses are not connected.")
|
|
1524
|
+
}
|
|
1516
1525
|
Bridge.log("MAN: Sending incidentId to glasses for log upload: \(incidentId)")
|
|
1517
|
-
|
|
1526
|
+
try driver.sendIncidentId(incidentId, apiBaseUrl: apiBaseUrl)
|
|
1518
1527
|
}
|
|
1519
1528
|
|
|
1520
1529
|
func sendWifiCredentials(_ ssid: String, _ password: String) {
|
|
1521
|
-
Bridge.log("MAN: Sending wifi credentials: \(ssid)
|
|
1530
|
+
Bridge.log("MAN: Sending wifi credentials: \(ssid)")
|
|
1522
1531
|
sgc?.sendWifiCredentials(ssid, password)
|
|
1523
1532
|
}
|
|
1524
1533
|
|
|
@@ -1738,6 +1747,9 @@ struct ViewState {
|
|
|
1738
1747
|
func setMicState() {
|
|
1739
1748
|
let willSendPcm = shouldSendPcm || shouldSendLc3
|
|
1740
1749
|
let willSendTranscript = shouldSendTranscript || localSttFallbackActive
|
|
1750
|
+
#if !os(macOS) && (!SWIFT_PACKAGE || MENTRA_FEATURE_LOCAL_STT)
|
|
1751
|
+
transcriber.setActive(willSendTranscript)
|
|
1752
|
+
#endif
|
|
1741
1753
|
micEnabled = willSendPcm || willSendTranscript
|
|
1742
1754
|
updateMicState()
|
|
1743
1755
|
}
|
|
@@ -1943,6 +1955,9 @@ struct ViewState {
|
|
|
1943
1955
|
sgc?.clearDisplay() // clear the screen
|
|
1944
1956
|
sgc?.disconnect()
|
|
1945
1957
|
sgc = nil // Clear the SGC reference after disconnect
|
|
1958
|
+
// This cache belongs to the discarded connection. Keep consumer demand,
|
|
1959
|
+
// but require a new mic-enable command when replacement glasses are ready.
|
|
1960
|
+
DeviceStore.shared.apply("glasses", "micEnabled", false)
|
|
1946
1961
|
resetSystemTimeSync()
|
|
1947
1962
|
resetMicHealth()
|
|
1948
1963
|
searching = false
|
|
@@ -1987,6 +2002,28 @@ struct ViewState {
|
|
|
1987
2002
|
controller = nil // Clear the controller reference after disconnect
|
|
1988
2003
|
}
|
|
1989
2004
|
|
|
2005
|
+
private var unpairInProgress = false
|
|
2006
|
+
|
|
2007
|
+
/// Explicit user Unpair, separate from passive pairing cleanup and logout.
|
|
2008
|
+
func unpair() async throws {
|
|
2009
|
+
guard !unpairInProgress else {
|
|
2010
|
+
throw NSError(domain: "NimoUnpair", code: 1,
|
|
2011
|
+
userInfo: [NSLocalizedDescriptionKey: "Unpair already in progress"])
|
|
2012
|
+
}
|
|
2013
|
+
unpairInProgress = true
|
|
2014
|
+
defer { unpairInProgress = false }
|
|
2015
|
+
if let nimo = sgc as? Nimo {
|
|
2016
|
+
_ = await withCheckedContinuation { (continuation: CheckedContinuation<Bool, Never>) in
|
|
2017
|
+
nimo.resetForUnpair { continuation.resume(returning: $0) }
|
|
2018
|
+
}
|
|
2019
|
+
guard (sgc as? Nimo) === nimo else {
|
|
2020
|
+
throw NSError(domain: "NimoUnpair", code: 2,
|
|
2021
|
+
userInfo: [NSLocalizedDescriptionKey: "Glasses changed during Unpair"])
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
forget()
|
|
2025
|
+
}
|
|
2026
|
+
|
|
1990
2027
|
func forget() {
|
|
1991
2028
|
Bridge.log("MAN: Forgetting smart glasses")
|
|
1992
2029
|
// Call forget first to stop timers/handlers/reconnect logic
|
|
@@ -2053,8 +2090,7 @@ struct ViewState {
|
|
|
2053
2090
|
#endif
|
|
2054
2091
|
// Clean up transcriber resources
|
|
2055
2092
|
#if !os(macOS) && (!SWIFT_PACKAGE || MENTRA_FEATURE_LOCAL_STT)
|
|
2056
|
-
transcriber
|
|
2057
|
-
transcriber = nil
|
|
2093
|
+
transcriber.shutdown()
|
|
2058
2094
|
#endif
|
|
2059
2095
|
|
|
2060
2096
|
// Clean up LC3 converter
|
|
@@ -94,6 +94,7 @@ class DeviceStore {
|
|
|
94
94
|
store.set("bluetooth", "gallery_mode", true)
|
|
95
95
|
store.set("bluetooth", "voice_activity_detection_enabled", BluetoothSdkDefaults.voiceActivityDetectionEnabled)
|
|
96
96
|
store.set("bluetooth", "loudness_gate_enabled", BluetoothSdkDefaults.loudnessGateEnabled)
|
|
97
|
+
store.set("bluetooth", "auto_power_off_enabled", BluetoothSdkDefaults.autoPowerOffEnabled)
|
|
97
98
|
// Mentra Nex feature flag (off by default; toggled from Nex Developer Settings):
|
|
98
99
|
store.set("bluetooth", "nex_chinese_captions", false)
|
|
99
100
|
store.set("bluetooth", "screen_disabled", false)
|
|
@@ -191,14 +192,25 @@ class DeviceStore {
|
|
|
191
192
|
|
|
192
193
|
// BLUETOOTH:
|
|
193
194
|
|
|
195
|
+
case ("bluetooth", "contextual_dashboard"):
|
|
196
|
+
if value is Bool {
|
|
197
|
+
Task { @MainActor in
|
|
198
|
+
if self.store.get("glasses", "headUp") as? Bool == true {
|
|
199
|
+
DeviceManager.shared.sendCurrentState()
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
194
204
|
case ("bluetooth", "brightness"):
|
|
195
205
|
let b = value as? Int ?? 50
|
|
196
206
|
let auto = store.get("bluetooth", "auto_brightness") as? Bool ?? true
|
|
197
207
|
Task {
|
|
198
|
-
DeviceManager.shared.sgc
|
|
199
|
-
|
|
208
|
+
guard let device = DeviceManager.shared.sgc else { return }
|
|
209
|
+
device.setBrightness(b, autoMode: auto)
|
|
210
|
+
guard device.showBrightnessConfirmation else { return }
|
|
211
|
+
await device.sendTextWall("Set brightness to \(b)%")
|
|
200
212
|
try? await Task.sleep(nanoseconds: 800_000_000) // 0.8 seconds
|
|
201
|
-
DeviceManager.shared.sgc
|
|
213
|
+
if (DeviceManager.shared.sgc as AnyObject?) === (device as AnyObject) { device.clearDisplay() }
|
|
202
214
|
}
|
|
203
215
|
|
|
204
216
|
case ("bluetooth", "auto_brightness"):
|
|
@@ -206,13 +218,14 @@ class DeviceStore {
|
|
|
206
218
|
let auto = value as? Bool ?? true
|
|
207
219
|
let autoBrightnessChanged = (oldValue as? Bool) != auto
|
|
208
220
|
Task {
|
|
209
|
-
DeviceManager.shared.sgc
|
|
210
|
-
|
|
211
|
-
|
|
221
|
+
guard let device = DeviceManager.shared.sgc else { return }
|
|
222
|
+
device.setBrightness(b, autoMode: auto)
|
|
223
|
+
if autoBrightnessChanged, device.showBrightnessConfirmation {
|
|
224
|
+
await device.sendTextWall(
|
|
212
225
|
auto ? "Enabled auto brightness" : "Disabled auto brightness"
|
|
213
226
|
)
|
|
214
227
|
try? await Task.sleep(nanoseconds: 800_000_000) // 0.8 seconds
|
|
215
|
-
DeviceManager.shared.sgc
|
|
228
|
+
if (DeviceManager.shared.sgc as AnyObject?) === (device as AnyObject) { device.clearDisplay() }
|
|
216
229
|
}
|
|
217
230
|
}
|
|
218
231
|
|
|
@@ -254,6 +267,12 @@ class DeviceStore {
|
|
|
254
267
|
case ("bluetooth", "loudness_gate_enabled"):
|
|
255
268
|
DeviceManager.shared.sgc?.sendLoudnessGateSetting()
|
|
256
269
|
|
|
270
|
+
case ("bluetooth", "auto_power_off_enabled"):
|
|
271
|
+
Bridge.log(
|
|
272
|
+
"DeviceStore: auto_power_off_enabled changed to \(String(describing: value)) — sending cs_swit type 11"
|
|
273
|
+
)
|
|
274
|
+
DeviceManager.shared.sgc?.sendAutoPowerOffSetting()
|
|
275
|
+
|
|
257
276
|
// Deliberately has no seeded default: the key starts absent so that a
|
|
258
277
|
// session where the engine has not authorized tuning can only ever
|
|
259
278
|
// send a reset.
|