@mentra/bluetooth-sdk 3.2.0-dev.168 → 3.2.0-dev.174
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 +32 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +4 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +63 -7
- package/android/src/main/java/com/mentra/bluetoothsdk/GeneratedReleaseMetadata.kt +5 -5
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +5 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/requests/DisplayRequests.kt +7 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/DashboardContentFormatterTest.kt +22 -0
- package/build/BluetoothSdk.types.d.ts +2 -0
- package/build/BluetoothSdk.types.d.ts.map +1 -1
- package/build/BluetoothSdk.types.js.map +1 -1
- package/build/_private/BluetoothSdkModule.d.ts +1 -0
- package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
- package/build/_private/BluetoothSdkModule.js.map +1 -1
- package/build/generated/releaseMetadata.js +5 -5
- 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/ios/BluetoothSdkModule.swift +5 -1
- package/ios/Source/BluetoothSdkDefaults.swift +1 -1
- package/ios/Source/DeviceManager.swift +155 -87
- package/ios/Source/GeneratedReleaseMetadata.swift +5 -5
- package/ios/Source/MentraBluetoothSDK.swift +5 -0
- package/ios/Source/requests/DisplayRequests.swift +8 -0
- package/ios/Tests/DashboardContentFormatterTests.swift +18 -0
- package/ios/Tests/DashboardRenderingTests.swift +168 -0
- package/package.json +1 -1
- package/scripts/public-dashboard-content-api.test.mjs +74 -0
- package/src/BluetoothSdk.types.ts +2 -0
- package/src/_private/BluetoothSdkModule.ts +1 -0
- package/src/generated/releaseMetadata.ts +5 -5
- package/src/index.ts +1 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
@testable import MentraBluetoothSDK
|
|
2
|
+
import XCTest
|
|
3
|
+
|
|
4
|
+
@MainActor
|
|
5
|
+
final class DashboardRenderingTests: XCTestCase {
|
|
6
|
+
private func setupDashboard() -> (DeviceManager, PausedDisplay) {
|
|
7
|
+
let manager = DeviceManager()
|
|
8
|
+
let display = PausedDisplay()
|
|
9
|
+
manager.sgc = display
|
|
10
|
+
DeviceStore.shared.apply("glasses", "fullyBooted", true)
|
|
11
|
+
DeviceStore.shared.apply("glasses", "headUp", true)
|
|
12
|
+
DeviceStore.shared.apply("bluetooth", "contextual_dashboard", true)
|
|
13
|
+
DeviceStore.shared.apply("bluetooth", "screen_disabled", false)
|
|
14
|
+
manager.sceneStates[1] = SceneFrame(appId: "test", epoch: 1, replay: false, elements: [SceneElement(id: "old", type: "text", x: 0, y: 0, w: 100, h: 30, text: "old", data: nil, border: 0, radius: 0, change: "created", contentHash: "old")], removed: [])
|
|
15
|
+
return (manager, display)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
func testSetterWaitsForCleanupAndLatestContentWins() async {
|
|
19
|
+
let (manager, display) = setupDashboard()
|
|
20
|
+
var firstReturned = false
|
|
21
|
+
let first = Task {
|
|
22
|
+
await manager.setDashboardContent("old")
|
|
23
|
+
firstReturned = true
|
|
24
|
+
}
|
|
25
|
+
await fulfillment(of: [display.cleanupStarted], timeout: 2)
|
|
26
|
+
XCTAssertFalse(firstReturned)
|
|
27
|
+
XCTAssertTrue(display.texts.isEmpty)
|
|
28
|
+
|
|
29
|
+
let secondStarted = XCTestExpectation(description: "second setter started")
|
|
30
|
+
let second = Task {
|
|
31
|
+
secondStarted.fulfill()
|
|
32
|
+
await manager.setDashboardContent("new")
|
|
33
|
+
}
|
|
34
|
+
await fulfillment(of: [secondStarted], timeout: 2)
|
|
35
|
+
display.finishCleanup()
|
|
36
|
+
await first.value
|
|
37
|
+
await second.value
|
|
38
|
+
XCTAssertFalse(display.texts.isEmpty)
|
|
39
|
+
XCTAssertTrue(display.texts.allSatisfy { $0.hasSuffix("new") })
|
|
40
|
+
XCTAssertEqual(display.cleanupCount, 1)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
func testHeadDownDuringCleanupRendersMainView() async {
|
|
44
|
+
let (manager, display) = setupDashboard()
|
|
45
|
+
manager.viewStates[0].text = "main view"
|
|
46
|
+
let update = Task { await manager.setDashboardContent("dashboard") }
|
|
47
|
+
await fulfillment(of: [display.cleanupStarted], timeout: 2)
|
|
48
|
+
DeviceStore.shared.apply("glasses", "headUp", false)
|
|
49
|
+
display.finishCleanup()
|
|
50
|
+
await update.value
|
|
51
|
+
XCTAssertEqual(display.texts, ["main view"])
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
func testSceneArrivingDuringCleanupReplacesText() async {
|
|
55
|
+
let (manager, display) = setupDashboard()
|
|
56
|
+
let update = Task { await manager.setDashboardContent("old") }
|
|
57
|
+
await fulfillment(of: [display.cleanupStarted], timeout: 2)
|
|
58
|
+
manager.displayEvent([
|
|
59
|
+
"view": "dashboard",
|
|
60
|
+
"scene": ["appId": "new-scene", "sceneEpoch": 2, "elements": [[String: Any]]()],
|
|
61
|
+
])
|
|
62
|
+
XCTAssertEqual(display.clearDisplayCount, 0)
|
|
63
|
+
XCTAssertTrue(display.scenes.isEmpty)
|
|
64
|
+
display.finishCleanup()
|
|
65
|
+
await update.value
|
|
66
|
+
await manager.sendCurrentState().value
|
|
67
|
+
XCTAssertTrue(display.texts.isEmpty)
|
|
68
|
+
XCTAssertFalse(display.scenes.isEmpty)
|
|
69
|
+
XCTAssertTrue(display.scenes.allSatisfy { $0.appId == "new-scene" })
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
@MainActor
|
|
74
|
+
private final class PausedDisplay: SGCManager {
|
|
75
|
+
var type = "Test display"
|
|
76
|
+
let hasMic = false
|
|
77
|
+
let cleanupStarted = XCTestExpectation(description: "cleanup suspended")
|
|
78
|
+
var cleanupCount = 0
|
|
79
|
+
var clearDisplayCount = 0
|
|
80
|
+
var texts: [String] = []
|
|
81
|
+
var scenes: [SceneFrame] = []
|
|
82
|
+
private var continuation: CheckedContinuation<Void, Never>?
|
|
83
|
+
|
|
84
|
+
func clearSceneElements(_: [String]) async {
|
|
85
|
+
cleanupCount += 1
|
|
86
|
+
await withCheckedContinuation { continuation in
|
|
87
|
+
self.continuation = continuation
|
|
88
|
+
cleanupStarted.fulfill()
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
func finishCleanup() {
|
|
93
|
+
continuation?.resume()
|
|
94
|
+
continuation = nil
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
func sendTextWall(_ text: String) async {
|
|
98
|
+
texts.append(text)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
func applySceneFrame(_ frame: SceneFrame) async {
|
|
102
|
+
scenes.append(frame)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
func clearDisplay() {
|
|
106
|
+
clearDisplayCount += 1
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Unused device capabilities for the display test double.
|
|
110
|
+
func setMicEnabled(_: Bool) {}
|
|
111
|
+
func sortMicRanking(list: [String]) -> [String] {
|
|
112
|
+
list
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
func sendJson(_: [String: Any], wakeUp _: Bool, requireAck _: Bool) {}
|
|
116
|
+
func requestPhoto(_: PhotoRequest) {}
|
|
117
|
+
func startStream(_: [String: Any]) {}
|
|
118
|
+
func stopStream() {}
|
|
119
|
+
func sendStreamKeepAlive(_: [String: Any]) {}
|
|
120
|
+
func startVideoRecording(requestId _: String, save _: Bool, sound _: Bool) {}
|
|
121
|
+
func stopVideoRecording(requestId _: String) {}
|
|
122
|
+
func sendButtonPhotoSettings() {}
|
|
123
|
+
func sendButtonVideoRecordingSettings() {}
|
|
124
|
+
func sendCameraFovSetting() {}
|
|
125
|
+
func sendButtonMaxRecordingTime() {}
|
|
126
|
+
func setBrightness(_: Int, autoMode _: Bool) {}
|
|
127
|
+
func sendText(_: String) async {}
|
|
128
|
+
func sendDoubleTextWall(_: String, _: String) async {}
|
|
129
|
+
func displayBitmap(base64ImageData _: String, x _: Int32?, y _: Int32?, width _: Int32?, height _: Int32?) async -> Bool {
|
|
130
|
+
false
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
func showDashboard() {}
|
|
134
|
+
func setDashboardPosition(_: Int, _: Int) {}
|
|
135
|
+
func setHeadUpAngle(_: Int) {}
|
|
136
|
+
func getBatteryStatus() {}
|
|
137
|
+
func setSilentMode(_: Bool) {}
|
|
138
|
+
func exit() {}
|
|
139
|
+
func sendShutdown() {}
|
|
140
|
+
func sendReboot() {}
|
|
141
|
+
func sendRgbLedControl(requestId _: String, packageName _: String?, action _: String, color _: String?, onDurationMs _: Int, offDurationMs _: Int, count _: Int) {}
|
|
142
|
+
func disconnect() {}
|
|
143
|
+
func forget() {}
|
|
144
|
+
func findCompatibleDevices() {}
|
|
145
|
+
func stopScan() {}
|
|
146
|
+
func connectById(_: String) {}
|
|
147
|
+
func getConnectedBluetoothName() -> String? {
|
|
148
|
+
nil
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
func cleanup() {}
|
|
152
|
+
func ping() {}
|
|
153
|
+
func dbg1() {}
|
|
154
|
+
func dbg2() {}
|
|
155
|
+
func connectController() {}
|
|
156
|
+
func disconnectController() {}
|
|
157
|
+
func requestWifiScan(scanId _: String?) {}
|
|
158
|
+
func sendWifiCredentials(_: String, _: String) {}
|
|
159
|
+
func forgetWifiNetwork(_: String) {}
|
|
160
|
+
func sendHotspotState(_: Bool) {}
|
|
161
|
+
func sendUserEmailToGlasses(_: String) {}
|
|
162
|
+
func sendOtaStart(otaVersionUrl _: String?) {}
|
|
163
|
+
func sendOtaQueryStatus() {}
|
|
164
|
+
func queryGalleryStatus() {}
|
|
165
|
+
func sendGalleryMode() {}
|
|
166
|
+
func requestVersionInfo() {}
|
|
167
|
+
func sendIncidentId(_: String, apiBaseUrl _: String?) {}
|
|
168
|
+
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import assert from "node:assert/strict"
|
|
2
|
+
import {readFileSync} from "node:fs"
|
|
3
|
+
import path from "node:path"
|
|
4
|
+
import test from "node:test"
|
|
5
|
+
import {fileURLToPath} from "node:url"
|
|
6
|
+
|
|
7
|
+
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..")
|
|
8
|
+
const readPackageFile = (relativePath) => readFileSync(path.join(packageRoot, relativePath), "utf8")
|
|
9
|
+
|
|
10
|
+
test("publishes setDashboardContent on React Native and native SDK surfaces", () => {
|
|
11
|
+
const publicTypes = readPackageFile("src/BluetoothSdk.types.ts")
|
|
12
|
+
const publicRoot = readPackageFile("src/index.ts")
|
|
13
|
+
const privateModule = readPackageFile("src/_private/BluetoothSdkModule.ts")
|
|
14
|
+
const androidModule = readPackageFile("android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt")
|
|
15
|
+
const androidSdk = readPackageFile("android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt")
|
|
16
|
+
const appleModule = readPackageFile("ios/BluetoothSdkModule.swift")
|
|
17
|
+
const appleSdk = readPackageFile("ios/Source/MentraBluetoothSDK.swift")
|
|
18
|
+
|
|
19
|
+
assert.match(publicTypes, /setDashboardContent\(content: string\): Promise<void>/)
|
|
20
|
+
assert.match(publicRoot, /setDashboardContent: bindPublicMethod\("setDashboardContent"\)/)
|
|
21
|
+
assert.match(privateModule, /setDashboardContent\(content: string\): Promise<void>/)
|
|
22
|
+
assert.match(androidModule, /SdkAsyncFunction\("setDashboardContent"\)/)
|
|
23
|
+
assert.match(androidSdk, /fun setDashboardContent\(content: String\)/)
|
|
24
|
+
assert.match(appleModule, /AsyncFunction\("setDashboardContent"\)/)
|
|
25
|
+
assert.match(appleSdk, /public func setDashboardContent\(_ content: String\) async/)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
test("keeps displayEvent internal and defers dashboard scene cleanup until rendering", () => {
|
|
29
|
+
const publicTypes = readPackageFile("src/BluetoothSdk.types.ts")
|
|
30
|
+
const publicRoot = readPackageFile("src/index.ts")
|
|
31
|
+
const androidSdk = readPackageFile("android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt")
|
|
32
|
+
const androidManager = readPackageFile("android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt")
|
|
33
|
+
const appleSdk = readPackageFile("ios/Source/MentraBluetoothSDK.swift")
|
|
34
|
+
const appleManager = readPackageFile("ios/Source/DeviceManager.swift")
|
|
35
|
+
|
|
36
|
+
assert.doesNotMatch(publicTypes, /\bdisplayEvent\(/)
|
|
37
|
+
assert.doesNotMatch(publicRoot, /\bdisplayEvent\b/)
|
|
38
|
+
assert.match(androidSdk, /internal fun displayEvent\(/)
|
|
39
|
+
assert.doesNotMatch(androidSdk, /public fun displayEvent\(/)
|
|
40
|
+
assert.match(appleSdk, /\n\s+func displayEvent\(/)
|
|
41
|
+
assert.doesNotMatch(appleSdk, /public func displayEvent\(/)
|
|
42
|
+
|
|
43
|
+
const androidSetter = androidManager.slice(
|
|
44
|
+
androidManager.indexOf("internal fun setDashboardContent"),
|
|
45
|
+
androidManager.indexOf("private fun clearPendingDashboardSceneElements"),
|
|
46
|
+
)
|
|
47
|
+
const appleSetter = appleManager.slice(
|
|
48
|
+
appleManager.indexOf("func setDashboardContent"),
|
|
49
|
+
appleManager.indexOf("private func clearPendingDashboardSceneElements"),
|
|
50
|
+
)
|
|
51
|
+
assert.match(androidSetter, /if \(headUp && contextualDashboard\)/)
|
|
52
|
+
assert.match(appleSetter, /if headUp && contextualDashboard/)
|
|
53
|
+
assert.match(androidSetter, /pendingDashboardSceneElementIds\.addAll/)
|
|
54
|
+
assert.match(appleSetter, /pendingDashboardSceneElementIds\.formUnion/)
|
|
55
|
+
assert.match(androidSetter, /dashboardSceneCleanupPending = true/)
|
|
56
|
+
assert.match(appleSetter, /dashboardSceneCleanupPending = true/)
|
|
57
|
+
assert.doesNotMatch(androidSetter, /clearSceneElements/)
|
|
58
|
+
assert.doesNotMatch(appleSetter, /clearSceneElements/)
|
|
59
|
+
assert.doesNotMatch(androidSetter, /showDashboard/)
|
|
60
|
+
assert.doesNotMatch(appleSetter, /showDashboard/)
|
|
61
|
+
|
|
62
|
+
assert.match(androidManager, /clearPendingDashboardSceneElements\(currentStateIndex\)/)
|
|
63
|
+
assert.match(appleManager, /await clearPendingDashboardSceneElements\(for:/)
|
|
64
|
+
assert.match(
|
|
65
|
+
androidManager,
|
|
66
|
+
/private fun dispatchSceneFrame[\s\S]*?clearPendingDashboardSceneElements\(stateIndex\)/,
|
|
67
|
+
)
|
|
68
|
+
assert.match(
|
|
69
|
+
appleManager,
|
|
70
|
+
/private func dispatchSceneFrame[\s\S]*?await (?:self\.)?renderCurrentState\(\)/,
|
|
71
|
+
)
|
|
72
|
+
assert.match(androidManager, /cleanupDeferred = stateIndex == 1 && dashboardSceneCleanupPending/)
|
|
73
|
+
assert.match(appleManager, /cleanupDeferred = stateIndex == 1 && dashboardSceneCleanupDeferred/)
|
|
74
|
+
})
|
|
@@ -1117,6 +1117,8 @@ export interface BluetoothSdkPublicModule {
|
|
|
1117
1117
|
|
|
1118
1118
|
displayText(text: string, x?: number, y?: number, size?: number): Promise<void>
|
|
1119
1119
|
clearDisplay(): Promise<void>
|
|
1120
|
+
/** Set session-only content below the dashboard status header. Pass an empty string to reset it. */
|
|
1121
|
+
setDashboardContent(content: string): Promise<void>
|
|
1120
1122
|
showDashboard(): Promise<void>
|
|
1121
1123
|
setDashboardPosition(height: number, depth: number): Promise<void>
|
|
1122
1124
|
setHeadUpAngle(angleDegrees: number): Promise<void>
|
|
@@ -77,6 +77,7 @@ declare class BluetoothSdkNativeModule extends NativeModule<BluetoothSdkModuleEv
|
|
|
77
77
|
displayEvent(params: Record<string, unknown>): Promise<void>
|
|
78
78
|
displayText(text: string, x?: number, y?: number, size?: number): Promise<void>
|
|
79
79
|
clearDisplay(): Promise<void>
|
|
80
|
+
setDashboardContent(content: string): Promise<void>
|
|
80
81
|
|
|
81
82
|
// Connection Commands
|
|
82
83
|
requestStatus(): Promise<void>
|
|
@@ -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.174",
|
|
16
|
+
"releaseSetId": "mentra-3.2.0-dev.174",
|
|
17
|
+
"sourceCommit": "f81a22e61cce70842fc7df5d611b33995f516b78",
|
|
18
|
+
"otaManifestUrl": "https://github.com/Mentra-Community/MentraOS/releases/download/mentra-builds-v3.2.0/mentra-live-ota-3.2.0-dev.174.json",
|
|
19
|
+
"otaManifestSha256": "f97e5acc0c793025b271450b85468b4dc1417ce315075dcee2b6e5efd21e0337"
|
|
20
20
|
})
|
package/src/index.ts
CHANGED
|
@@ -105,6 +105,7 @@ export const BluetoothSdk: BluetoothSdkPublicModule = Object.freeze({
|
|
|
105
105
|
forget: bindPublicMethod("forget"),
|
|
106
106
|
displayText: bindPublicMethod("displayText"),
|
|
107
107
|
clearDisplay: bindPublicMethod("clearDisplay"),
|
|
108
|
+
setDashboardContent: bindPublicMethod("setDashboardContent"),
|
|
108
109
|
showDashboard: bindPublicMethod("showDashboard"),
|
|
109
110
|
setDashboardPosition: bindPublicMethod("setDashboardPosition"),
|
|
110
111
|
setHeadUpAngle: bindPublicMethod("setHeadUpAngle"),
|