@mentra/bluetooth-sdk 0.1.21-beta.0 → 0.1.21-beta.2
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 +22 -11
- package/android/build.gradle +3 -0
- package/android/src/main/AndroidManifest.xml +3 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkAnalytics.kt +34 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkDefaults.kt +1 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +43 -3
- package/android/src/main/java/com/mentra/bluetoothsdk/Bridge.kt +7 -2
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +82 -6
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +5 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +32 -3
- package/android/src/main/java/com/mentra/bluetoothsdk/OtaManifest.kt +43 -3
- package/android/src/main/java/com/mentra/bluetoothsdk/camera/CameraModels.kt +17 -4
- package/android/src/main/java/com/mentra/bluetoothsdk/debug/BleTraceLogger.kt +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/services/Foreground.kt +53 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Ar99.java +2653 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.kt +161 -14
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/SGCManager.kt +3 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/ar99/ota/Ar99OtaManager.java +566 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/ar99/ota/OtaByteUtils.java +18 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/ar99/ota/OtaCommandConstants.java +35 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/ar99/ota/OtaCrc32Util.java +13 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/ar99/ota/OtaGattTransport.java +11 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/ar99/ota/OtaProtocol.java +92 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/streaming/StreamModels.kt +41 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/types/DeviceModels.kt +7 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/BleJsonCompact.java +8 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/Constants.kt +2 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/PhoneAudioMonitor.kt +45 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/audio/Ar99OpusPcmDecoder.java +398 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/OtaManifestDowngradeTest.kt +86 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/StreamStatusEventTest.kt +29 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/camera/PhotoRequestTest.kt +30 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/Ar99AdvertisementMatcherTest.java +22 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/utils/BleJsonCompactTest.java +20 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/utils/PhoneAudioMonitorTest.kt +45 -0
- package/build/BluetoothSdk.types.d.ts +58 -25
- package/build/BluetoothSdk.types.d.ts.map +1 -1
- package/build/BluetoothSdk.types.js +2 -1
- package/build/BluetoothSdk.types.js.map +1 -1
- package/build/_internal.d.ts +1 -0
- package/build/_internal.d.ts.map +1 -1
- package/build/_internal.js +1 -0
- package/build/_internal.js.map +1 -1
- package/build/_private/BluetoothSdkModule.d.ts +8 -2
- package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
- package/build/_private/BluetoothSdkModule.js +6 -3
- package/build/_private/BluetoothSdkModule.js.map +1 -1
- package/build/_private/cameraRequestPayload.d.ts +1 -1
- package/build/_private/cameraRequestPayload.d.ts.map +1 -1
- package/build/_private/cameraRequestPayload.js +6 -0
- package/build/_private/cameraRequestPayload.js.map +1 -1
- package/build/_private/photoRequestPayload.d.ts.map +1 -1
- package/build/_private/photoRequestPayload.js +10 -1
- package/build/_private/photoRequestPayload.js.map +1 -1
- package/build/_private/sdkOtaManifest.d.ts +20 -0
- package/build/_private/sdkOtaManifest.d.ts.map +1 -0
- package/build/_private/sdkOtaManifest.js +28 -0
- package/build/_private/sdkOtaManifest.js.map +1 -0
- package/build/index.d.ts +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +7 -1
- package/build/index.js.map +1 -1
- package/ios/BluetoothSdkModule.swift +41 -2
- package/ios/Source/BluetoothSdkDefaults.swift +2 -1
- package/ios/Source/Bridge.swift +15 -3
- package/ios/Source/DeviceManager.swift +67 -17
- package/ios/Source/DeviceStore.swift +5 -1
- package/ios/Source/MentraBluetoothSDK.swift +37 -8
- package/ios/Source/OtaManifest.swift +47 -4
- package/ios/Source/camera/CameraModels.swift +20 -5
- package/ios/Source/internal/BleTraceLogger.swift +1 -1
- package/ios/Source/internal/BluetoothSdkAnalytics.swift +39 -0
- package/ios/Source/sgcs/Ar99.swift +2451 -0
- package/ios/Source/sgcs/MentraLive.swift +174 -38
- package/ios/Source/sgcs/SGCManager.swift +8 -0
- package/ios/Source/sgcs/ar99/ota/Ar99OtaManager.swift +637 -0
- package/ios/Source/streaming/StreamModels.swift +71 -7
- package/ios/Source/types/DeviceModels.swift +13 -0
- package/ios/Source/utils/BleJsonCompact.swift +8 -0
- package/ios/Source/utils/Constants.swift +2 -0
- package/ios/Source/utils/PhoneAudioMonitor.swift +4 -0
- package/package.json +1 -1
- package/src/BluetoothSdk.types.ts +61 -28
- package/src/_internal.ts +1 -0
- package/src/_private/BluetoothSdkModule.ts +20 -6
- package/src/_private/cameraRequestPayload.ts +8 -2
- package/src/_private/photoRequestPayload.ts +12 -2
- package/src/_private/sdkOtaManifest.ts +31 -0
- package/src/index.ts +9 -1
|
@@ -217,25 +217,79 @@ public struct StreamResolvedConfig: Equatable {
|
|
|
217
217
|
}
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
+
private func streamInt64Value(_ value: Any?) -> Int64? {
|
|
221
|
+
if let int64 = value as? Int64 { return int64 }
|
|
222
|
+
if let int = value as? Int { return Int64(int) }
|
|
223
|
+
if let number = value as? NSNumber { return number.int64Value }
|
|
224
|
+
return nil
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/// Live encoder and device telemetry reported by the glasses while streaming.
|
|
228
|
+
public struct StreamLiveStats: Equatable {
|
|
229
|
+
public let bitrate: Int64?
|
|
230
|
+
public let fps: Double?
|
|
231
|
+
public let droppedFrames: Int64?
|
|
232
|
+
public let duration: Int64?
|
|
233
|
+
public let temperatureC: Double?
|
|
234
|
+
|
|
235
|
+
public init(
|
|
236
|
+
bitrate: Int64? = nil,
|
|
237
|
+
fps: Double? = nil,
|
|
238
|
+
droppedFrames: Int64? = nil,
|
|
239
|
+
duration: Int64? = nil,
|
|
240
|
+
temperatureC: Double? = nil
|
|
241
|
+
) {
|
|
242
|
+
self.bitrate = bitrate
|
|
243
|
+
self.fps = fps
|
|
244
|
+
self.droppedFrames = droppedFrames
|
|
245
|
+
self.duration = duration
|
|
246
|
+
self.temperatureC = temperatureC
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
init?(values: [String: Any]?) {
|
|
250
|
+
guard let values else { return nil }
|
|
251
|
+
self.init(
|
|
252
|
+
bitrate: streamInt64Value(values["bitrate"]),
|
|
253
|
+
fps: doubleValue(values["fps"]),
|
|
254
|
+
droppedFrames: streamInt64Value(values["droppedFrames"]),
|
|
255
|
+
duration: streamInt64Value(values["duration"]),
|
|
256
|
+
temperatureC: doubleValue(values["temperatureC"])
|
|
257
|
+
)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
var values: [String: Any] {
|
|
261
|
+
var values: [String: Any] = [:]
|
|
262
|
+
if let bitrate { values["bitrate"] = bitrate }
|
|
263
|
+
if let fps { values["fps"] = fps }
|
|
264
|
+
if let droppedFrames { values["droppedFrames"] = droppedFrames }
|
|
265
|
+
if let duration { values["duration"] = duration }
|
|
266
|
+
if let temperatureC { values["temperatureC"] = temperatureC }
|
|
267
|
+
return values
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
220
271
|
public struct StreamRequest {
|
|
221
272
|
public let streamUrl: String
|
|
222
273
|
public let streamId: String
|
|
223
274
|
public let sound: Bool
|
|
224
275
|
public let video: StreamVideoConfig?
|
|
225
276
|
public let audio: StreamAudioConfig?
|
|
277
|
+
public let authToken: String?
|
|
226
278
|
|
|
227
279
|
public init(
|
|
228
280
|
streamUrl: String,
|
|
229
281
|
streamId: String = "",
|
|
230
282
|
sound: Bool = true,
|
|
231
283
|
video: StreamVideoConfig? = nil,
|
|
232
|
-
audio: StreamAudioConfig? = nil
|
|
284
|
+
audio: StreamAudioConfig? = nil,
|
|
285
|
+
authToken: String? = nil
|
|
233
286
|
) {
|
|
234
287
|
self.streamUrl = streamUrl
|
|
235
288
|
self.streamId = streamId
|
|
236
289
|
self.sound = sound
|
|
237
290
|
self.video = video
|
|
238
291
|
self.audio = audio
|
|
292
|
+
self.authToken = authToken
|
|
239
293
|
}
|
|
240
294
|
|
|
241
295
|
init(values: [String: Any]) {
|
|
@@ -248,7 +302,8 @@ public struct StreamRequest {
|
|
|
248
302
|
streamId: values["streamId"] as? String ?? "",
|
|
249
303
|
sound: values["sound"] as? Bool ?? true,
|
|
250
304
|
video: StreamVideoConfig(values: values["video"] as? [String: Any]),
|
|
251
|
-
audio: StreamAudioConfig(values: values["audio"] as? [String: Any])
|
|
305
|
+
audio: StreamAudioConfig(values: values["audio"] as? [String: Any]),
|
|
306
|
+
authToken: values["authToken"] as? String ?? values["auth_token"] as? String
|
|
252
307
|
)
|
|
253
308
|
}
|
|
254
309
|
|
|
@@ -264,6 +319,9 @@ public struct StreamRequest {
|
|
|
264
319
|
if let audioValues = audio?.dictionary, !audioValues.isEmpty {
|
|
265
320
|
values["audio"] = audioValues
|
|
266
321
|
}
|
|
322
|
+
if let authToken, !authToken.isEmpty {
|
|
323
|
+
values["authToken"] = authToken
|
|
324
|
+
}
|
|
267
325
|
return values
|
|
268
326
|
}
|
|
269
327
|
}
|
|
@@ -553,18 +611,21 @@ public enum StreamStatus: CustomStringConvertible, Equatable {
|
|
|
553
611
|
|
|
554
612
|
public struct StreamStatusEvent: CustomStringConvertible {
|
|
555
613
|
public let status: StreamStatus
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
614
|
+
public let stats: StreamLiveStats?
|
|
615
|
+
/// True when the glasses will retry the failed publisher themselves
|
|
616
|
+
/// (emitting side lands in PR #3488); absent on older firmware and on
|
|
617
|
+
/// events not parsed from a glasses status map. Carried here instead of
|
|
618
|
+
/// as an `.error` associated value so the public case stays unchanged.
|
|
560
619
|
public private(set) var willRetry: Bool?
|
|
561
620
|
|
|
562
|
-
public init(status: StreamStatus) {
|
|
621
|
+
public init(status: StreamStatus, stats: StreamLiveStats? = nil) {
|
|
563
622
|
self.status = status
|
|
623
|
+
self.stats = stats
|
|
564
624
|
}
|
|
565
625
|
|
|
566
626
|
public init(values: [String: Any]) {
|
|
567
627
|
status = StreamStatus(values: values)
|
|
628
|
+
stats = StreamLiveStats(values: values["stats"] as? [String: Any])
|
|
568
629
|
willRetry = boolValue(values, "willRetry")
|
|
569
630
|
}
|
|
570
631
|
|
|
@@ -583,6 +644,9 @@ public struct StreamStatusEvent: CustomStringConvertible {
|
|
|
583
644
|
public var values: [String: Any] {
|
|
584
645
|
var values = status.values
|
|
585
646
|
values["type"] = "stream_status"
|
|
647
|
+
if let stats {
|
|
648
|
+
values["stats"] = stats.values
|
|
649
|
+
}
|
|
586
650
|
if let willRetry {
|
|
587
651
|
values["willRetry"] = willRetry
|
|
588
652
|
}
|
|
@@ -19,6 +19,7 @@ public enum DeviceModel: String {
|
|
|
19
19
|
case z100
|
|
20
20
|
case frame
|
|
21
21
|
case nimo
|
|
22
|
+
case ar99
|
|
22
23
|
case simulated
|
|
23
24
|
case r1
|
|
24
25
|
|
|
@@ -40,6 +41,8 @@ public enum DeviceModel: String {
|
|
|
40
41
|
DeviceTypes.FRAME
|
|
41
42
|
case .nimo:
|
|
42
43
|
DeviceTypes.NIMO
|
|
44
|
+
case .ar99:
|
|
45
|
+
DeviceTypes.AR99
|
|
43
46
|
case .simulated:
|
|
44
47
|
DeviceTypes.SIMULATED
|
|
45
48
|
case .r1:
|
|
@@ -65,6 +68,8 @@ public enum DeviceModel: String {
|
|
|
65
68
|
.frame
|
|
66
69
|
case DeviceTypes.NIMO:
|
|
67
70
|
.nimo
|
|
71
|
+
case DeviceTypes.AR99:
|
|
72
|
+
.ar99
|
|
68
73
|
case DeviceTypes.SIMULATED:
|
|
69
74
|
.simulated
|
|
70
75
|
case ControllerTypes.R1:
|
|
@@ -80,6 +85,7 @@ public struct Device: Identifiable, Equatable, CustomStringConvertible {
|
|
|
80
85
|
public let name: String
|
|
81
86
|
/// CoreBluetooth identifier when available.
|
|
82
87
|
public let identifier: String?
|
|
88
|
+
public let projectName: String?
|
|
83
89
|
public let rssi: Int?
|
|
84
90
|
/// Stable app-facing scan-result key. Do not parse; use typed fields instead.
|
|
85
91
|
public let id: String
|
|
@@ -88,12 +94,14 @@ public struct Device: Identifiable, Equatable, CustomStringConvertible {
|
|
|
88
94
|
model: DeviceModel,
|
|
89
95
|
name: String,
|
|
90
96
|
identifier: String? = nil,
|
|
97
|
+
projectName: String? = nil,
|
|
91
98
|
rssi: Int? = nil,
|
|
92
99
|
id: String? = nil
|
|
93
100
|
) {
|
|
94
101
|
self.model = model
|
|
95
102
|
self.name = name
|
|
96
103
|
self.identifier = identifier
|
|
104
|
+
self.projectName = projectName
|
|
97
105
|
self.rssi = rssi
|
|
98
106
|
self.id = id ?? identifier.flatMap { $0.isEmpty ? nil : $0 } ?? "\(model.deviceType):\(name)"
|
|
99
107
|
}
|
|
@@ -111,6 +119,9 @@ public struct Device: Identifiable, Equatable, CustomStringConvertible {
|
|
|
111
119
|
if let identifier, !identifier.isEmpty {
|
|
112
120
|
values["address"] = identifier
|
|
113
121
|
}
|
|
122
|
+
if let projectName, !projectName.isEmpty {
|
|
123
|
+
values["projectName"] = projectName
|
|
124
|
+
}
|
|
114
125
|
if let rssi {
|
|
115
126
|
values["rssi"] = rssi
|
|
116
127
|
}
|
|
@@ -121,11 +132,13 @@ public struct Device: Identifiable, Equatable, CustomStringConvertible {
|
|
|
121
132
|
guard let model = stringValue(values, "model") else { return nil }
|
|
122
133
|
guard let name = stringValue(values, "name") else { return nil }
|
|
123
134
|
let identifier = stringValue(values, "address").flatMap { $0.isEmpty ? nil : $0 }
|
|
135
|
+
let projectName = stringValue(values, "projectName").flatMap { $0.isEmpty ? nil : $0 }
|
|
124
136
|
let rssi = intValue(values["rssi"])
|
|
125
137
|
self.init(
|
|
126
138
|
model: DeviceModel.fromDeviceType(model),
|
|
127
139
|
name: name,
|
|
128
140
|
identifier: identifier,
|
|
141
|
+
projectName: projectName,
|
|
129
142
|
rssi: rssi,
|
|
130
143
|
id: stringValue(values, "id")
|
|
131
144
|
)
|
|
@@ -19,6 +19,14 @@ enum BleJsonCompact {
|
|
|
19
19
|
"transferMethod": "tm",
|
|
20
20
|
"manual": "m",
|
|
21
21
|
"reconnecting": "rc",
|
|
22
|
+
"stats": "st",
|
|
23
|
+
"width": "w",
|
|
24
|
+
"height": "h",
|
|
25
|
+
"bitrate": "br",
|
|
26
|
+
"fps": "f",
|
|
27
|
+
"droppedFrames": "df",
|
|
28
|
+
"duration": "du",
|
|
29
|
+
"temperatureC": "tc",
|
|
22
30
|
]
|
|
23
31
|
|
|
24
32
|
private static let shortToLong: [String: String] = {
|
|
@@ -8,6 +8,7 @@ struct DeviceTypes {
|
|
|
8
8
|
static let NEX = "Mentra Display"
|
|
9
9
|
static let FRAME = "Brilliant Frame"
|
|
10
10
|
static let NIMO = "NIMO"
|
|
11
|
+
static let AR99 = "AR99"
|
|
11
12
|
|
|
12
13
|
static let ALL = [
|
|
13
14
|
SIMULATED,
|
|
@@ -19,6 +20,7 @@ struct DeviceTypes {
|
|
|
19
20
|
NEX,
|
|
20
21
|
FRAME,
|
|
21
22
|
NIMO,
|
|
23
|
+
AR99,
|
|
22
24
|
]
|
|
23
25
|
|
|
24
26
|
/// Private init to prevent instantiation
|
|
@@ -55,6 +55,10 @@ class PhoneAudioMonitor {
|
|
|
55
55
|
return session.isOtherAudioPlaying || ownAppAudioPlaying
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
func isOwnAppAudioPlaying() -> Bool {
|
|
59
|
+
ownAppAudioPlaying
|
|
60
|
+
}
|
|
61
|
+
|
|
58
62
|
/// Notify the monitor that our own app started/stopped playing audio
|
|
59
63
|
/// Called from RN AudioPlaybackService via Bridge
|
|
60
64
|
func setOwnAppAudioPlaying(_ playing: Bool) {
|
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@ export type GlassesNotReadyEvent = {
|
|
|
5
5
|
}
|
|
6
6
|
|
|
7
7
|
// NOTE: unlike most events below, the native module does NOT include a `type`
|
|
8
|
-
// field on the button_press payload
|
|
8
|
+
// field on the button_press payload 闂?it sends only {buttonId, pressType,
|
|
9
9
|
// timestamp} (see BluetoothSdkModule on both iOS and Android). Consumers must
|
|
10
10
|
// filter on `pressType` / the "button_press" listener name, never `event.type`.
|
|
11
11
|
export type ButtonPressEvent = {
|
|
@@ -85,7 +85,7 @@ export function createDisconnectedGlassesStatus(): Partial<GlassesStatus> {
|
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
/** K900 `sr_getvol` response (Mentra Live glasses media step volume 0
|
|
88
|
+
/** K900 `sr_getvol` response (Mentra Live glasses media step volume 0闂?5). */
|
|
89
89
|
export type GlassesMediaVolumeGetResult = {
|
|
90
90
|
level: number
|
|
91
91
|
statusCode: number
|
|
@@ -119,11 +119,11 @@ export type WifiStatusChangeEvent = WifiStatus & {
|
|
|
119
119
|
/**
|
|
120
120
|
* Glasses-reported provisioning failure reason when THIS event is the verdict of a
|
|
121
121
|
* failed connect attempt; absent on routine link-state updates. An attempt property,
|
|
122
|
-
* not a link property
|
|
122
|
+
* not a link property 闂?which is why it lives on the event, not on WifiStatus:
|
|
123
123
|
* "connect_timeout" arrives on a disconnected status (never associated), while
|
|
124
124
|
* "connected_to_other_network" arrives on a *connected* status (the attempt failed
|
|
125
125
|
* and the glasses ended up on / fell back to a different SSID than requested).
|
|
126
|
-
* Requires ASG client v40+
|
|
126
|
+
* Requires ASG client v40+ 闂?older glasses never send it.
|
|
127
127
|
*/
|
|
128
128
|
error?: string
|
|
129
129
|
}
|
|
@@ -453,17 +453,21 @@ export type RgbLedAction = "on" | "off"
|
|
|
453
453
|
export type RgbLedColor = "red" | "green" | "blue" | "orange" | "white"
|
|
454
454
|
export type PhotoSize = "low" | "medium" | "high" | "max"
|
|
455
455
|
export type PhotoMode = "photo" | "text"
|
|
456
|
+
export type PhotoTransferMethod = "auto" | "direct" | "ble"
|
|
456
457
|
export type ButtonPhotoSize = "low" | "medium" | "high" | "max"
|
|
457
458
|
|
|
458
459
|
/**
|
|
459
460
|
* @deprecated Sticky action-button photo presets via {@link BluetoothSdkPublicModule.setPhotoCaptureDefaults}
|
|
460
461
|
* are deprecated. Prefer per-request {@link BluetoothSdkPublicModule.requestPhoto} options
|
|
461
|
-
* (e.g. `mode: "text"` for
|
|
462
|
+
* (e.g. `mode: "text"` for text sensor size/crop, or explicit `aeExposureDivisor`) instead of
|
|
463
|
+
* persisting button-photo tuning on the glasses.
|
|
462
464
|
*/
|
|
463
465
|
export type PhotoCaptureDefaults = {
|
|
464
466
|
size?: PhotoSize
|
|
465
|
-
|
|
467
|
+
/** ZSL preview buffering for physical camera-button photos. */
|
|
466
468
|
zsl?: boolean
|
|
469
|
+
/** MFNR still capture for physical camera-button photos. */
|
|
470
|
+
mfnr?: boolean
|
|
467
471
|
noiseReduction?: boolean
|
|
468
472
|
edgeEnhancement?: boolean
|
|
469
473
|
ispDigitalGain?: number
|
|
@@ -509,6 +513,7 @@ export const DeviceModels = {
|
|
|
509
513
|
Z100: "Vuzix Z100",
|
|
510
514
|
Frame: "Brilliant Frame",
|
|
511
515
|
Nimo: "NIMO",
|
|
516
|
+
Ar99: "AR99",
|
|
512
517
|
R1: "Even Realities R1",
|
|
513
518
|
} as const
|
|
514
519
|
|
|
@@ -523,7 +528,7 @@ export type DashboardMenuItem = {
|
|
|
523
528
|
|
|
524
529
|
export const CAMERA_FOV_MIN = 62
|
|
525
530
|
export const CAMERA_FOV_MAX = 118
|
|
526
|
-
export const CAMERA_FOV_DEFAULT =
|
|
531
|
+
export const CAMERA_FOV_DEFAULT = CAMERA_FOV_MAX
|
|
527
532
|
|
|
528
533
|
export type CameraRoiPosition = "center" | "bottom" | "top"
|
|
529
534
|
export type CameraRoiPositionValue = 0 | 1 | 2
|
|
@@ -570,8 +575,8 @@ export type PhotoRequestParams = {
|
|
|
570
575
|
appId?: string
|
|
571
576
|
size: PhotoSize
|
|
572
577
|
mode?: PhotoMode
|
|
573
|
-
/** `ble` skips direct upload and forces phone-relayed
|
|
574
|
-
transferMethod?:
|
|
578
|
+
/** `direct` disables BLE fallback; `ble` skips direct upload and forces phone-relayed transfer. */
|
|
579
|
+
transferMethod?: PhotoTransferMethod
|
|
575
580
|
webhookUrl: string | null
|
|
576
581
|
authToken: string | null
|
|
577
582
|
compress: PhotoCompression
|
|
@@ -587,8 +592,10 @@ export type PhotoRequestParams = {
|
|
|
587
592
|
/** Requested on wire; glasses may log not_implemented. */
|
|
588
593
|
noiseReduction?: boolean
|
|
589
594
|
edgeEnhancement?: boolean
|
|
590
|
-
|
|
595
|
+
/** ZSL buffering. Forced off for manual/scan stills because fixed sensor controls take priority. */
|
|
591
596
|
zsl?: boolean
|
|
597
|
+
/** MFNR still capture. Forced off for manual/scan stills because fixed sensor controls take priority. */
|
|
598
|
+
mfnr?: boolean
|
|
592
599
|
ispDigitalGain?: number
|
|
593
600
|
ispAnalogGain?: string
|
|
594
601
|
}
|
|
@@ -601,6 +608,10 @@ export type WarmUpCameraParams = {
|
|
|
601
608
|
exposureTimeNs?: number | null
|
|
602
609
|
/** Ready-state hold; defaults to 15 seconds and is capped at 60 seconds by ASG. */
|
|
603
610
|
durationMs?: number
|
|
611
|
+
/** ZSL preview buffering for the warm-up session. */
|
|
612
|
+
zsl?: boolean
|
|
613
|
+
/** MFNR still capture for the warm-up session. */
|
|
614
|
+
mfnr?: boolean
|
|
604
615
|
}
|
|
605
616
|
|
|
606
617
|
export type StreamVideoConfig = {
|
|
@@ -721,7 +732,7 @@ export type StreamResolvedConfig = {
|
|
|
721
732
|
}
|
|
722
733
|
}
|
|
723
734
|
|
|
724
|
-
/** Live encoder telemetry
|
|
735
|
+
/** Live encoder and device telemetry emitted periodically by supported glasses firmware. */
|
|
725
736
|
export type StreamLiveStats = {
|
|
726
737
|
/** Current encoded video bitrate in bits per second. */
|
|
727
738
|
bitrate?: number
|
|
@@ -730,7 +741,7 @@ export type StreamLiveStats = {
|
|
|
730
741
|
droppedFrames?: number
|
|
731
742
|
/** Seconds since the stream started. */
|
|
732
743
|
duration?: number
|
|
733
|
-
/** Device temperature in
|
|
744
|
+
/** Device temperature in 闂佺娅i悡? if the hardware reports it. */
|
|
734
745
|
temperatureC?: number
|
|
735
746
|
}
|
|
736
747
|
|
|
@@ -790,6 +801,17 @@ export type MtkUpdateCompleteEvent = {
|
|
|
790
801
|
timestamp: number
|
|
791
802
|
}
|
|
792
803
|
|
|
804
|
+
/**
|
|
805
|
+
* The glasses process restarted while the BES kept the BLE link alive (its `sid`
|
|
806
|
+
* changed, or first appeared after an update from a pre-sid build). There is no
|
|
807
|
+
* physical disconnect for this — treat it as the logical reconnect edge.
|
|
808
|
+
*/
|
|
809
|
+
export type GlassesSessionChangedEvent = {
|
|
810
|
+
type: "glasses_session_changed"
|
|
811
|
+
previous_sid: string
|
|
812
|
+
sid: string
|
|
813
|
+
}
|
|
814
|
+
|
|
793
815
|
/** @deprecated Glasses no longer emit ota_progress; use {@link OtaStatusEvent} and status-store mapping. */
|
|
794
816
|
export type OtaProgressEvent = {
|
|
795
817
|
type: "ota_progress"
|
|
@@ -806,16 +828,6 @@ export type OtaStartAckEvent = {
|
|
|
806
828
|
type: "ota_start_ack"
|
|
807
829
|
timestamp: number
|
|
808
830
|
}
|
|
809
|
-
|
|
810
|
-
export type OtaUpdateAvailableEvent = {
|
|
811
|
-
type: "ota_update_available"
|
|
812
|
-
version_code?: number
|
|
813
|
-
version_name?: string
|
|
814
|
-
updates?: string[]
|
|
815
|
-
total_size?: number
|
|
816
|
-
cache_ready?: boolean
|
|
817
|
-
}
|
|
818
|
-
|
|
819
831
|
export type OtaStatusEvent = {
|
|
820
832
|
type: "ota_status"
|
|
821
833
|
session_id: string
|
|
@@ -894,9 +906,10 @@ export type BluetoothSdkModuleEvents = {
|
|
|
894
906
|
stream_status: (event: StreamStatusEvent) => void
|
|
895
907
|
keep_alive_ack: (event: KeepAliveAckEvent) => void
|
|
896
908
|
mtk_update_complete: (event: MtkUpdateCompleteEvent) => void
|
|
897
|
-
|
|
909
|
+
glasses_session_changed: (event: GlassesSessionChangedEvent) => void
|
|
898
910
|
ota_start_ack: (event: OtaStartAckEvent) => void
|
|
899
911
|
ota_status: (event: OtaStatusEvent) => void
|
|
912
|
+
ar99_ota_status: (event: Ar99OtaStatusEvent) => void
|
|
900
913
|
version_info: (event: VersionInfoEvent) => void
|
|
901
914
|
send_command_to_ble: (event: BleCommandTraceEvent) => void
|
|
902
915
|
receive_command_from_ble: (event: BleCommandTraceEvent) => void
|
|
@@ -910,6 +923,16 @@ export interface ExtractionProgressEvent {
|
|
|
910
923
|
totalBytes: number
|
|
911
924
|
}
|
|
912
925
|
|
|
926
|
+
export interface Ar99OtaStatusEvent {
|
|
927
|
+
type: "ar99_ota_status"
|
|
928
|
+
phase: string
|
|
929
|
+
progress: number
|
|
930
|
+
offset: number
|
|
931
|
+
total: number
|
|
932
|
+
errorMessage?: string
|
|
933
|
+
error_message?: string
|
|
934
|
+
}
|
|
935
|
+
|
|
913
936
|
export interface PhoneNotificationEvent {
|
|
914
937
|
notificationId: string
|
|
915
938
|
app: string
|
|
@@ -982,9 +1005,9 @@ export type BluetoothSdkEventMap = {
|
|
|
982
1005
|
mic_pcm: MicPcmEvent
|
|
983
1006
|
mic_lc3: MicLc3Event
|
|
984
1007
|
stream_status: StreamStatusEvent
|
|
985
|
-
ota_update_available: OtaUpdateAvailableEvent
|
|
986
1008
|
ota_start_ack: OtaStartAckEvent
|
|
987
1009
|
ota_status: OtaStatusEvent
|
|
1010
|
+
ar99_ota_status: Ar99OtaStatusEvent
|
|
988
1011
|
version_info: VersionInfoEvent
|
|
989
1012
|
extraction_progress: ExtractionProgressEvent
|
|
990
1013
|
}
|
|
@@ -1036,9 +1059,10 @@ export interface BluetoothSdkPublicModule {
|
|
|
1036
1059
|
|
|
1037
1060
|
setGalleryModeEnabled(enabled: boolean): Promise<SettingsAckSuccessEvent>
|
|
1038
1061
|
setVoiceActivityDetectionEnabled(enabled: boolean): Promise<void>
|
|
1062
|
+
setLoudnessGateEnabled(enabled: boolean): Promise<void>
|
|
1039
1063
|
/**
|
|
1040
1064
|
* @deprecated Sticky action-button photo presets are deprecated. Prefer per-request
|
|
1041
|
-
* `requestPhoto(...)` options (e.g. `mode: "text"` for
|
|
1065
|
+
* `requestPhoto(...)` options (e.g. `mode: "text"` for text sensor size/crop, or explicit per-shot
|
|
1042
1066
|
* fields). Still functional until removed in a future release.
|
|
1043
1067
|
*/
|
|
1044
1068
|
setPhotoCaptureDefaults(settings: PhotoCaptureDefaults): Promise<SettingsAckSuccessEvent>
|
|
@@ -1078,7 +1102,7 @@ export interface BluetoothSdkPublicModule {
|
|
|
1078
1102
|
* Stop the active recording. When {@link webhookUrl} is provided, the glasses
|
|
1079
1103
|
* upload the recorded video to it (multipart) using {@link authToken}. These
|
|
1080
1104
|
* are supplied at stop time (not start) so the token is fresh when the upload
|
|
1081
|
-
* runs
|
|
1105
|
+
* runs 闂?a recording can last arbitrarily long. An empty/omitted webhook keeps
|
|
1082
1106
|
* the video on device (no upload).
|
|
1083
1107
|
*/
|
|
1084
1108
|
stopVideoRecording(
|
|
@@ -1111,6 +1135,10 @@ export interface BluetoothSdkPublicModule {
|
|
|
1111
1135
|
checkForOtaUpdate(): Promise<boolean>
|
|
1112
1136
|
/** Start the OTA flow with the same configured manifest URL used by checkForOtaUpdate(). */
|
|
1113
1137
|
startOtaUpdate(): Promise<OtaStartAckEvent>
|
|
1138
|
+
startAr99OtaFromFile(path: string): Promise<boolean>
|
|
1139
|
+
cancelAr99Ota(): Promise<void>
|
|
1140
|
+
sendAr99FactoryReset(): Promise<void>
|
|
1141
|
+
buildAr99OtaSignature(secret: string, appName: string, currentVersion: string, serialNumber: string, nonce: string): string
|
|
1114
1142
|
|
|
1115
1143
|
// // stt commands (MOVE TO CRUST)
|
|
1116
1144
|
// setSttModelDetails(path: string, languageCode: string): Promise<void>
|
|
@@ -1173,6 +1201,8 @@ export interface OtaUpdateInfo {
|
|
|
1173
1201
|
updates: string[] // ["apk", "mtk", "bes"]
|
|
1174
1202
|
totalSize: number
|
|
1175
1203
|
cacheReady?: boolean
|
|
1204
|
+
/** True when the APK step installs an older build than the glasses currently run (exact-pin manifests only). */
|
|
1205
|
+
isDowngrade?: boolean
|
|
1176
1206
|
}
|
|
1177
1207
|
|
|
1178
1208
|
export interface OtaProgress {
|
|
@@ -1257,13 +1287,15 @@ export interface Device {
|
|
|
1257
1287
|
/**
|
|
1258
1288
|
* Stable app-facing key for this scan result, within the limits of the
|
|
1259
1289
|
* platform identifier available to the SDK. Do not parse this value; use the
|
|
1260
|
-
* typed model, name, address, and rssi fields instead.
|
|
1290
|
+
* typed model, name, address, projectName, and rssi fields instead.
|
|
1261
1291
|
*/
|
|
1262
1292
|
id: string
|
|
1263
1293
|
model: DeviceModel
|
|
1264
1294
|
name: string
|
|
1265
1295
|
/** Platform address/identifier when available: Android Bluetooth address, iOS CoreBluetooth identifier. */
|
|
1266
1296
|
address?: string
|
|
1297
|
+
/** Optional AR99 project discriminator. Supported value: AR99. */
|
|
1298
|
+
projectName?: string
|
|
1267
1299
|
/**
|
|
1268
1300
|
* Optional scan signal strength. It may be undefined at first discovery and
|
|
1269
1301
|
* appear in a later scan update when the platform reports RSSI metadata.
|
|
@@ -1294,7 +1326,7 @@ export interface WifiSearchResult {
|
|
|
1294
1326
|
ssid: string
|
|
1295
1327
|
requiresPassword: boolean
|
|
1296
1328
|
signalStrength: number
|
|
1297
|
-
/** Frequency in MHz (from glasses scan). 5 GHz band is typically 5170
|
|
1329
|
+
/** Frequency in MHz (from glasses scan). 5 GHz band is typically 5170闂?825. Omitted if unknown. */
|
|
1298
1330
|
frequency?: number
|
|
1299
1331
|
}
|
|
1300
1332
|
|
|
@@ -1347,6 +1379,7 @@ export type BluetoothSettingsUpdate = Partial<{
|
|
|
1347
1379
|
twelve_hour_time: boolean
|
|
1348
1380
|
gallery_mode: boolean
|
|
1349
1381
|
voice_activity_detection_enabled: boolean
|
|
1382
|
+
loudness_gate_enabled: boolean
|
|
1350
1383
|
button_photo_size: ButtonPhotoSize
|
|
1351
1384
|
button_video_settings: {width: number; height: number; fps: number}
|
|
1352
1385
|
button_video_width: number
|
package/src/_internal.ts
CHANGED
|
@@ -11,3 +11,4 @@ export type {BluetoothSdkInternalModule} from "./_private/BluetoothSdkModule"
|
|
|
11
11
|
export * from "./BluetoothSdk.types"
|
|
12
12
|
export {default as MentraLocalNetwork} from "./_private/MentraLocalNetworkModule"
|
|
13
13
|
export * from "./_private/MentraLocalNetworkModule"
|
|
14
|
+
export {BLUETOOTH_SDK_VERSION, sdkPinnedOtaManifestUrl} from "./_private/sdkOtaManifest"
|
|
@@ -125,9 +125,11 @@ declare class BluetoothSdkNativeModule extends NativeModule<BluetoothSdkModuleEv
|
|
|
125
125
|
// Gallery Commands
|
|
126
126
|
setGalleryModeEnabled(enabled: boolean): Promise<SettingsAckSuccessEvent>
|
|
127
127
|
setVoiceActivityDetectionEnabled(enabled: boolean): Promise<void>
|
|
128
|
+
/** Mentra Live center-mic loudness / Barrier gate (cs_swit type 10). */
|
|
129
|
+
setLoudnessGateEnabled(enabled: boolean): Promise<void>
|
|
128
130
|
/**
|
|
129
131
|
* @deprecated Sticky action-button photo presets are deprecated. Prefer per-request
|
|
130
|
-
* `requestPhoto(...)` options (e.g. `mode: "text"` for
|
|
132
|
+
* `requestPhoto(...)` options (e.g. `mode: "text"` for text sensor size/crop).
|
|
131
133
|
*/
|
|
132
134
|
setPhotoCaptureDefaults(settings: PhotoCaptureDefaults): Promise<SettingsAckSuccessEvent>
|
|
133
135
|
setVideoRecordingDefaults(width: number, height: number, fps: number): Promise<SettingsAckSuccessEvent>
|
|
@@ -157,6 +159,10 @@ declare class BluetoothSdkNativeModule extends NativeModule<BluetoothSdkModuleEv
|
|
|
157
159
|
checkForOtaUpdate(): Promise<boolean>
|
|
158
160
|
startOtaUpdate(otaVersionUrl?: string | null): Promise<OtaStartAckEvent>
|
|
159
161
|
sendOtaQueryStatus(): Promise<OtaQueryResult>
|
|
162
|
+
startAr99OtaFromFile(path: string): Promise<boolean>
|
|
163
|
+
cancelAr99Ota(): Promise<void>
|
|
164
|
+
sendAr99FactoryReset(): Promise<void>
|
|
165
|
+
buildAr99OtaSignature(secret: string, appName: string, currentVersion: string, serialNumber: string, nonce: string): string
|
|
160
166
|
|
|
161
167
|
// Version Info Commands
|
|
162
168
|
requestVersionInfo(): Promise<VersionInfoResult>
|
|
@@ -191,7 +197,7 @@ declare class BluetoothSdkNativeModule extends NativeModule<BluetoothSdkModuleEv
|
|
|
191
197
|
setOwnAppAudioPlaying(playing: boolean): Promise<void>
|
|
192
198
|
|
|
193
199
|
// Live PCM output stream (miniapp speaker.createStream). MentraOS-internal
|
|
194
|
-
//
|
|
200
|
+
// ? 16-bit LE PCM chunks into a streaming AudioTrack (USAGE_MEDIA, so it
|
|
195
201
|
// follows the phone's media route, e.g. A2DP to glasses). Implemented with
|
|
196
202
|
// AudioTrack on Android and AVAudioEngine on iOS.
|
|
197
203
|
/** Open a PCM stream session. One AudioTrack per id; caller manages ids. */
|
|
@@ -209,7 +215,7 @@ declare class BluetoothSdkNativeModule extends NativeModule<BluetoothSdkModuleEv
|
|
|
209
215
|
|
|
210
216
|
/** Mentra Live only: K900 `cs_getvol` / `sr_getvol`. */
|
|
211
217
|
getGlassesMediaVolume(): Promise<GlassesMediaVolumeGetResult>
|
|
212
|
-
/** Mentra Live only: K900 `cs_vol` / `sr_vol`; level clamped 0
|
|
218
|
+
/** Mentra Live only: K900 `cs_vol` / `sr_vol`; level clamped 0??5 on native. */
|
|
213
219
|
setGlassesMediaVolume(level: number): Promise<GlassesMediaVolumeSetResult>
|
|
214
220
|
|
|
215
221
|
// RGB LED Control
|
|
@@ -270,7 +276,7 @@ const DEFAULT_SCAN_TIMEOUT_MS = 15_000
|
|
|
270
276
|
function bindNativeMethod<T extends (...args: never[]) => unknown>(module: Record<string, unknown>, name: string): T {
|
|
271
277
|
const method = module[name]
|
|
272
278
|
if (typeof method !== "function") {
|
|
273
|
-
console.warn(`[BluetoothSdk] Native method "${name}" is unavailable
|
|
279
|
+
console.warn(`[BluetoothSdk] Native method "${name}" is unavailable ??rebuild the app (bun android / bun ios)`)
|
|
274
280
|
return (async () => {
|
|
275
281
|
throw new Error(`BluetoothSdk.${name} is not available in this native build. Rebuild the app.`)
|
|
276
282
|
}) as T
|
|
@@ -287,10 +293,10 @@ const CAMERA_ROI_POSITION_VALUES: Record<CameraRoiPosition, CameraFovSetting["ro
|
|
|
287
293
|
}
|
|
288
294
|
|
|
289
295
|
// Named presets are a convenience layer over the numeric {fov, roiPosition} API.
|
|
290
|
-
//
|
|
296
|
+
// The default is the full sensor; "standard" preserves the historical 102� crop.
|
|
291
297
|
const CAMERA_FOV_PRESETS: Record<CameraFovPreset, CameraFovSetting> = {
|
|
292
298
|
narrow: {fov: 82, roiPosition: 0},
|
|
293
|
-
standard: {fov:
|
|
299
|
+
standard: {fov: 102, roiPosition: 0},
|
|
294
300
|
wide: {fov: CAMERA_FOV_MAX, roiPosition: 0},
|
|
295
301
|
}
|
|
296
302
|
|
|
@@ -486,6 +492,10 @@ NativeBluetoothSdkModule.setVoiceActivityDetectionEnabled = function (enabled: b
|
|
|
486
492
|
return this.updateBluetoothSettings({voice_activity_detection_enabled: enabled})
|
|
487
493
|
}
|
|
488
494
|
|
|
495
|
+
NativeBluetoothSdkModule.setLoudnessGateEnabled = function (enabled: boolean) {
|
|
496
|
+
return this.updateBluetoothSettings({loudness_gate_enabled: enabled})
|
|
497
|
+
}
|
|
498
|
+
|
|
489
499
|
const nativeSetCameraFov = bindNativeMethod<(fov: CameraFovSetting) => MaybePromise<CameraFovResult>>(
|
|
490
500
|
NativeBluetoothSdkModule as unknown as Record<string, unknown>,
|
|
491
501
|
"setCameraFov",
|
|
@@ -639,3 +649,7 @@ NativeBluetoothSdkModule.warmUpCamera = function (params: WarmUpCameraParams) {
|
|
|
639
649
|
|
|
640
650
|
export default NativeBluetoothSdkModule
|
|
641
651
|
export const BluetoothSdk = NativeBluetoothSdkModule as BluetoothSdkInternalModule
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
|
|
655
|
+
|
|
@@ -9,8 +9,8 @@ function nonBlankString(value?: string | null): string | undefined {
|
|
|
9
9
|
/** Expo Android bridge rejects null values in Map<String, Any> — omit optional nullish fields. */
|
|
10
10
|
export function warmUpCameraParamsForNative(
|
|
11
11
|
params: WarmUpCameraParams,
|
|
12
|
-
): Record<string, string | number> {
|
|
13
|
-
const payload: Record<string, string | number> = {
|
|
12
|
+
): Record<string, string | number | boolean> {
|
|
13
|
+
const payload: Record<string, string | number | boolean> = {
|
|
14
14
|
size: normalizePhotoSizeTier(params.size),
|
|
15
15
|
mode: params.mode ?? "photo",
|
|
16
16
|
}
|
|
@@ -26,5 +26,11 @@ export function warmUpCameraParamsForNative(
|
|
|
26
26
|
if (durationMs != null && Number.isFinite(durationMs) && durationMs > 0) {
|
|
27
27
|
payload.durationMs = Math.round(durationMs)
|
|
28
28
|
}
|
|
29
|
+
if (params.mfnr != null) {
|
|
30
|
+
payload.mfnr = params.mfnr
|
|
31
|
+
}
|
|
32
|
+
if (params.zsl != null) {
|
|
33
|
+
payload.zsl = params.zsl
|
|
34
|
+
}
|
|
29
35
|
return payload
|
|
30
36
|
}
|
|
@@ -1,4 +1,14 @@
|
|
|
1
|
-
import type {PhotoRequestParams, PhotoSize} from "../BluetoothSdk.types"
|
|
1
|
+
import type {PhotoRequestParams, PhotoSize, PhotoTransferMethod} from "../BluetoothSdk.types"
|
|
2
|
+
|
|
3
|
+
const PHOTO_TRANSFER_METHODS = new Set<PhotoTransferMethod>(["auto", "direct", "ble"])
|
|
4
|
+
|
|
5
|
+
function photoTransferMethodForNative(value: unknown): PhotoTransferMethod {
|
|
6
|
+
if (value === undefined) return "auto"
|
|
7
|
+
if (typeof value === "string" && PHOTO_TRANSFER_METHODS.has(value as PhotoTransferMethod)) {
|
|
8
|
+
return value as PhotoTransferMethod
|
|
9
|
+
}
|
|
10
|
+
throw new TypeError(`Invalid transferMethod ${JSON.stringify(value)}. Expected "auto", "direct", or "ble".`)
|
|
11
|
+
}
|
|
2
12
|
|
|
3
13
|
/** Maps unknown/legacy size strings to the current wire format. */
|
|
4
14
|
export function normalizePhotoSizeTier(size: string | undefined): PhotoSize {
|
|
@@ -29,7 +39,7 @@ export function photoRequestParamsForNative(params: PhotoRequestParams): Record<
|
|
|
29
39
|
const payload: Record<string, string | number | boolean> = {
|
|
30
40
|
size: normalizePhotoSizeTier(params.size),
|
|
31
41
|
mode: params.mode ?? "photo",
|
|
32
|
-
transferMethod: params.transferMethod
|
|
42
|
+
transferMethod: photoTransferMethodForNative(params.transferMethod),
|
|
33
43
|
webhookUrl: params.webhookUrl ?? "",
|
|
34
44
|
compress: params.compress,
|
|
35
45
|
sound: params.sound,
|