@mentra/bluetooth-sdk 0.1.20 → 0.1.21-beta.0
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 +5 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +127 -24
- package/android/src/main/java/com/mentra/bluetoothsdk/Bridge.kt +65 -14
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +46 -3
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +369 -97
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraLocalNetworkModule.kt +333 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/camera/CameraModels.kt +3 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G1.kt +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G2.kt +16 -9
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Mach1.kt +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.kt +183 -30
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraNex.kt +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Nimo.kt +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/SGCManager.kt +4 -2
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Simulated.kt +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/status/WifiHotspotStatus.kt +19 -3
- package/android/src/main/java/com/mentra/bluetoothsdk/streaming/StreamModels.kt +15 -2
- package/android/src/main/java/com/mentra/bluetoothsdk/tts/TTSTools.kt +115 -47
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/audio/PcmStreamPlayer.kt +328 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/camera/PhotoRequestTest.kt +15 -0
- package/build/BluetoothSdk.types.d.ts +46 -1
- package/build/BluetoothSdk.types.d.ts.map +1 -1
- package/build/BluetoothSdk.types.js.map +1 -1
- package/build/_internal.d.ts +2 -0
- package/build/_internal.d.ts.map +1 -1
- package/build/_internal.js +2 -0
- package/build/_internal.js.map +1 -1
- package/build/_private/BluetoothSdkModule.d.ts +23 -2
- package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
- package/build/_private/BluetoothSdkModule.js +20 -0
- package/build/_private/BluetoothSdkModule.js.map +1 -1
- package/build/_private/MentraLocalNetworkModule.d.ts +37 -0
- package/build/_private/MentraLocalNetworkModule.d.ts.map +1 -0
- package/build/_private/MentraLocalNetworkModule.js +3 -0
- package/build/_private/MentraLocalNetworkModule.js.map +1 -0
- package/build/_private/cameraRequestPayload.d.ts.map +1 -1
- package/build/_private/cameraRequestPayload.js +1 -0
- package/build/_private/cameraRequestPayload.js.map +1 -1
- package/build/_private/photoRequestPayload.d.ts.map +1 -1
- package/build/_private/photoRequestPayload.js +1 -0
- package/build/_private/photoRequestPayload.js.map +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +4 -0
- package/build/index.js.map +1 -1
- package/expo-module.config.json +1 -0
- package/ios/BluetoothSdkModule.swift +72 -0
- package/ios/Source/BluetoothSdkDefaults.swift +1 -1
- package/ios/Source/Bridge.swift +72 -18
- package/ios/Source/DeviceManager.swift +51 -3
- package/ios/Source/DeviceStore.swift +1 -1
- package/ios/Source/MentraBluetoothSDK.swift +289 -25
- package/ios/Source/audio/PcmStreamPlayer.swift +410 -0
- package/ios/Source/camera/CameraModels.swift +8 -3
- package/ios/Source/sgcs/G1.swift +1 -1
- package/ios/Source/sgcs/G2.swift +19 -7
- package/ios/Source/sgcs/Mach1.swift +1 -1
- package/ios/Source/sgcs/MentraLive.swift +318 -37
- package/ios/Source/sgcs/MentraNex.swift +1 -1
- package/ios/Source/sgcs/Nimo.swift +1 -1
- package/ios/Source/sgcs/SGCManager.swift +5 -2
- package/ios/Source/sgcs/Simulated.swift +1 -1
- package/ios/Source/status/WifiHotspotStatus.swift +21 -2
- package/ios/Source/streaming/StreamModels.swift +9 -0
- package/ios/Source/tts/TTSTools.swift +1 -1
- package/package.json +1 -2
- package/src/BluetoothSdk.types.ts +49 -0
- package/src/_internal.ts +2 -0
- package/src/_private/BluetoothSdkModule.ts +64 -13
- package/src/_private/MentraLocalNetworkModule.ts +50 -0
- package/src/_private/cameraRequestPayload.ts +1 -0
- package/src/_private/photoRequestPayload.ts +2 -3
- package/src/index.ts +4 -0
|
@@ -133,20 +133,39 @@ public enum WifiStatus: CustomStringConvertible, Equatable {
|
|
|
133
133
|
public struct WifiStatusEvent: CustomStringConvertible {
|
|
134
134
|
public let status: WifiStatus
|
|
135
135
|
|
|
136
|
-
|
|
136
|
+
/// Glasses-reported provisioning failure reason when THIS event is the verdict of a
|
|
137
|
+
/// failed connect attempt; nil for routine link-state updates. An attempt property,
|
|
138
|
+
/// not a link property — which is why it lives on the event and not on `WifiStatus`:
|
|
139
|
+
/// "connect_timeout" arrives with a disconnected status (never associated), while
|
|
140
|
+
/// "connected_to_other_network" arrives with a *connected* status — the attempt
|
|
141
|
+
/// failed and Android's auto-join left the glasses on (or returned them to) a
|
|
142
|
+
/// different SSID than requested, so the link is genuinely up while the request
|
|
143
|
+
/// genuinely failed. Sent by ASG client builds that include the WiFi error
|
|
144
|
+
/// surfacing (v40+); older glasses never set it.
|
|
145
|
+
public let error: String?
|
|
146
|
+
|
|
147
|
+
public init(status: WifiStatus, error: String? = nil) {
|
|
137
148
|
self.status = status
|
|
149
|
+
self.error = error
|
|
138
150
|
}
|
|
139
151
|
|
|
140
152
|
init(connected: Bool, ssid: String?, localIp: String?) {
|
|
141
153
|
status = WifiStatus.fromStoreFields(connected: connected, ssid: ssid, localIp: localIp) ?? .disconnected
|
|
154
|
+
error = nil
|
|
142
155
|
}
|
|
143
156
|
|
|
144
157
|
init(values: [String: Any]) {
|
|
145
158
|
status = WifiStatus(values: values) ?? .disconnected
|
|
159
|
+
let rawError = stringValue(values, "error")
|
|
160
|
+
error = rawError?.isEmpty == false ? rawError : nil
|
|
146
161
|
}
|
|
147
162
|
|
|
148
163
|
public var values: [String: Any] {
|
|
149
|
-
status.values.merging(["type": "wifi_status_change"]) { _, new in new }
|
|
164
|
+
var body = status.values.merging(["type": "wifi_status_change"]) { _, new in new }
|
|
165
|
+
if let error {
|
|
166
|
+
body["error"] = error
|
|
167
|
+
}
|
|
168
|
+
return body
|
|
150
169
|
}
|
|
151
170
|
|
|
152
171
|
public var description: String {
|
|
@@ -553,6 +553,11 @@ public enum StreamStatus: CustomStringConvertible, Equatable {
|
|
|
553
553
|
|
|
554
554
|
public struct StreamStatusEvent: CustomStringConvertible {
|
|
555
555
|
public let status: StreamStatus
|
|
556
|
+
// True when the glasses will retry the failed publisher themselves
|
|
557
|
+
// (emitting side lands in PR #3488); absent on older firmware and on
|
|
558
|
+
// events not parsed from a glasses status map. Carried here instead of
|
|
559
|
+
// as an `.error` associated value so the public case stays unchanged.
|
|
560
|
+
public private(set) var willRetry: Bool?
|
|
556
561
|
|
|
557
562
|
public init(status: StreamStatus) {
|
|
558
563
|
self.status = status
|
|
@@ -560,6 +565,7 @@ public struct StreamStatusEvent: CustomStringConvertible {
|
|
|
560
565
|
|
|
561
566
|
public init(values: [String: Any]) {
|
|
562
567
|
status = StreamStatus(values: values)
|
|
568
|
+
willRetry = boolValue(values, "willRetry")
|
|
563
569
|
}
|
|
564
570
|
|
|
565
571
|
public var state: StreamState {
|
|
@@ -577,6 +583,9 @@ public struct StreamStatusEvent: CustomStringConvertible {
|
|
|
577
583
|
public var values: [String: Any] {
|
|
578
584
|
var values = status.values
|
|
579
585
|
values["type"] = "stream_status"
|
|
586
|
+
if let willRetry {
|
|
587
|
+
values["willRetry"] = willRetry
|
|
588
|
+
}
|
|
580
589
|
return values
|
|
581
590
|
}
|
|
582
591
|
|
|
@@ -111,7 +111,7 @@ class TTSTools {
|
|
|
111
111
|
voiceStyle: (modelPath as NSString).appendingPathComponent(kFileVoiceStyle)
|
|
112
112
|
)
|
|
113
113
|
var modelConfig = sherpaOnnxOfflineTtsModelConfig(
|
|
114
|
-
numThreads:
|
|
114
|
+
numThreads: 8,
|
|
115
115
|
supertonic: supertonic
|
|
116
116
|
)
|
|
117
117
|
var config = sherpaOnnxOfflineTtsConfig(model: modelConfig, maxNumSentences: 1)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mentra/bluetooth-sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.21-beta.0",
|
|
4
4
|
"description": "SDK for communicating with smart glasses",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"react-native": "src/index.ts",
|
|
@@ -105,7 +105,6 @@
|
|
|
105
105
|
"access": "public",
|
|
106
106
|
"registry": "https://registry.npmjs.org/"
|
|
107
107
|
},
|
|
108
|
-
"dependencies": {},
|
|
109
108
|
"devDependencies": {
|
|
110
109
|
"@types/node": "^25.9.3",
|
|
111
110
|
"expo-module-scripts": "^55.0.2",
|
|
@@ -116,6 +116,16 @@ export function isConnectedWifiStatus(status: WifiStatus): status is ConnectedWi
|
|
|
116
116
|
|
|
117
117
|
export type WifiStatusChangeEvent = WifiStatus & {
|
|
118
118
|
type: "wifi_status_change"
|
|
119
|
+
/**
|
|
120
|
+
* Glasses-reported provisioning failure reason when THIS event is the verdict of a
|
|
121
|
+
* failed connect attempt; absent on routine link-state updates. An attempt property,
|
|
122
|
+
* not a link property — which is why it lives on the event, not on WifiStatus:
|
|
123
|
+
* "connect_timeout" arrives on a disconnected status (never associated), while
|
|
124
|
+
* "connected_to_other_network" arrives on a *connected* status (the attempt failed
|
|
125
|
+
* and the glasses ended up on / fell back to a different SSID than requested).
|
|
126
|
+
* Requires ASG client v40+ — older glasses never send it.
|
|
127
|
+
*/
|
|
128
|
+
error?: string
|
|
119
129
|
}
|
|
120
130
|
|
|
121
131
|
export type HotspotStatus = {state: "disabled"} | {state: "enabled"; ssid: string; password: string; localIp: string}
|
|
@@ -405,6 +415,7 @@ export type SettingsAckSetting =
|
|
|
405
415
|
| "button_video_recording"
|
|
406
416
|
| "button_max_recording_time"
|
|
407
417
|
| "camera_fov"
|
|
418
|
+
| "camera_fov_override"
|
|
408
419
|
| "camera_tuning"
|
|
409
420
|
|
|
410
421
|
export type SettingsAckEvent = {
|
|
@@ -416,6 +427,7 @@ export type SettingsAckEvent = {
|
|
|
416
427
|
fov?: number
|
|
417
428
|
roiPosition?: CameraRoiPositionValue
|
|
418
429
|
hardwareApplied?: boolean
|
|
430
|
+
leaseId?: string
|
|
419
431
|
active?: boolean
|
|
420
432
|
size?: ButtonPhotoSize | string
|
|
421
433
|
width?: number
|
|
@@ -533,6 +545,13 @@ export type CameraFovResult = {
|
|
|
533
545
|
timestamp: number
|
|
534
546
|
}
|
|
535
547
|
|
|
548
|
+
export type CameraFovOverrideRequest = CameraFovRequest & {
|
|
549
|
+
/** Phone-owned lease used to make delayed releases safe. */
|
|
550
|
+
leaseId: string
|
|
551
|
+
/** Safety TTL; refresh the same lease/configuration to extend without a HAL restart. */
|
|
552
|
+
ttlMs?: number
|
|
553
|
+
}
|
|
554
|
+
|
|
536
555
|
export type CameraFovSetting = {
|
|
537
556
|
fov: number
|
|
538
557
|
roiPosition: CameraRoiPositionValue
|
|
@@ -551,6 +570,8 @@ export type PhotoRequestParams = {
|
|
|
551
570
|
appId?: string
|
|
552
571
|
size: PhotoSize
|
|
553
572
|
mode?: PhotoMode
|
|
573
|
+
/** `ble` skips direct upload and forces phone-relayed BLE image transfer. */
|
|
574
|
+
transferMethod?: "auto" | "ble"
|
|
554
575
|
webhookUrl: string | null
|
|
555
576
|
authToken: string | null
|
|
556
577
|
compress: PhotoCompression
|
|
@@ -573,9 +594,12 @@ export type PhotoRequestParams = {
|
|
|
573
594
|
}
|
|
574
595
|
|
|
575
596
|
export type WarmUpCameraParams = {
|
|
597
|
+
/** Supply this when the owner needs to call stopCameraWarmUp during teardown. */
|
|
576
598
|
requestId?: string
|
|
577
599
|
size: PhotoSize
|
|
600
|
+
mode?: PhotoMode
|
|
578
601
|
exposureTimeNs?: number | null
|
|
602
|
+
/** Ready-state hold; defaults to 15 seconds and is capped at 60 seconds by ASG. */
|
|
579
603
|
durationMs?: number
|
|
580
604
|
}
|
|
581
605
|
|
|
@@ -838,6 +862,8 @@ export type BluetoothSdkModuleEvents = {
|
|
|
838
862
|
speaking_status: (event: SpeakingStatusEvent) => void
|
|
839
863
|
battery_status: (event: BatteryStatusEvent) => void
|
|
840
864
|
local_transcription: (event: LocalTranscriptionEvent) => void
|
|
865
|
+
phone_notification: (event: PhoneNotificationEvent) => void
|
|
866
|
+
phone_notification_dismissed: (event: PhoneNotificationDismissedEvent) => void
|
|
841
867
|
wifi_status_change: (event: WifiStatusChangeEvent) => void
|
|
842
868
|
wifi_scan_result: (event: WifiScanResultEvent) => void
|
|
843
869
|
hotspot_status_change: (event: HotspotStatusChangeEvent) => void
|
|
@@ -884,6 +910,23 @@ export interface ExtractionProgressEvent {
|
|
|
884
910
|
totalBytes: number
|
|
885
911
|
}
|
|
886
912
|
|
|
913
|
+
export interface PhoneNotificationEvent {
|
|
914
|
+
notificationId: string
|
|
915
|
+
app: string
|
|
916
|
+
title: string
|
|
917
|
+
content: string
|
|
918
|
+
priority: string
|
|
919
|
+
timestamp: number
|
|
920
|
+
packageName: string
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
export interface PhoneNotificationDismissedEvent {
|
|
924
|
+
notificationId: string
|
|
925
|
+
notificationKey: string
|
|
926
|
+
packageName: string
|
|
927
|
+
timestamp: number
|
|
928
|
+
}
|
|
929
|
+
|
|
887
930
|
export type PublicGlassesStatus = Omit<
|
|
888
931
|
GlassesStatus,
|
|
889
932
|
"otaUpdateAvailable" | "otaProgress" | "otaInProgress" | "otaVersionUrl"
|
|
@@ -1002,6 +1045,10 @@ export interface BluetoothSdkPublicModule {
|
|
|
1002
1045
|
setVideoRecordingDefaults(settings: VideoRecordingDefaults): Promise<SettingsAckSuccessEvent>
|
|
1003
1046
|
setMaxVideoRecordingDuration(minutes: number): Promise<SettingsAckSuccessEvent>
|
|
1004
1047
|
setCameraFov(request: CameraFovRequest): Promise<CameraFovResult>
|
|
1048
|
+
/** One-way FOV command for legacy ASG clients that do not send settings acknowledgements. */
|
|
1049
|
+
setLegacyCameraFov(request: CameraFovRequest): Promise<CameraFovResult>
|
|
1050
|
+
setCameraFovOverride(request: CameraFovOverrideRequest): Promise<CameraFovResult>
|
|
1051
|
+
releaseCameraFovOverride(leaseId: string): Promise<SettingsAckSuccessEvent>
|
|
1005
1052
|
/**
|
|
1006
1053
|
* Configure camera HAL tuning (ANR / gain) on Mentra Live glasses.
|
|
1007
1054
|
*
|
|
@@ -1019,6 +1066,8 @@ export interface BluetoothSdkPublicModule {
|
|
|
1019
1066
|
queryGalleryStatus(): Promise<GalleryStatusEvent>
|
|
1020
1067
|
requestPhoto(params: PhotoRequestParams): Promise<PhotoSuccessResponseEvent>
|
|
1021
1068
|
warmUpCamera(params: WarmUpCameraParams): Promise<CameraStatusEvent>
|
|
1069
|
+
/** Release one request-owned warm-up. Opening requests reject with camera_warm_up_cancelled. */
|
|
1070
|
+
stopCameraWarmUp(requestId: string): Promise<void>
|
|
1022
1071
|
startVideoRecording(
|
|
1023
1072
|
requestId: string,
|
|
1024
1073
|
save: boolean,
|
package/src/_internal.ts
CHANGED
|
@@ -9,3 +9,5 @@
|
|
|
9
9
|
export {default} from "./_private/BluetoothSdkModule"
|
|
10
10
|
export type {BluetoothSdkInternalModule} from "./_private/BluetoothSdkModule"
|
|
11
11
|
export * from "./BluetoothSdk.types"
|
|
12
|
+
export {default as MentraLocalNetwork} from "./_private/MentraLocalNetworkModule"
|
|
13
|
+
export * from "./_private/MentraLocalNetworkModule"
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
CAMERA_FOV_MIN,
|
|
12
12
|
CameraFovPreset,
|
|
13
13
|
CameraFovRequest,
|
|
14
|
+
CameraFovOverrideRequest,
|
|
14
15
|
CameraFovResult,
|
|
15
16
|
CameraFovSetting,
|
|
16
17
|
CameraRoiPosition,
|
|
@@ -116,7 +117,7 @@ declare class BluetoothSdkNativeModule extends NativeModule<BluetoothSdkModuleEv
|
|
|
116
117
|
sendWifiCredentials(ssid: string, password: string): Promise<WifiStatusChangeEvent>
|
|
117
118
|
forgetWifiNetwork(ssid: string): Promise<WifiStatusChangeEvent>
|
|
118
119
|
setHotspotState(enabled: boolean): Promise<HotspotStatusChangeEvent>
|
|
119
|
-
/** Set glasses system clock
|
|
120
|
+
/** Set the glasses system clock when phone detects clock skew (Mentra Live and G2). */
|
|
120
121
|
setSystemTime(timestampMs: number): Promise<void>
|
|
121
122
|
/** Logs current WiFi frequency (MHz) and 5 GHz band to Android logcat. */
|
|
122
123
|
logCurrentWifiFrequency(): Promise<void>
|
|
@@ -132,6 +133,10 @@ declare class BluetoothSdkNativeModule extends NativeModule<BluetoothSdkModuleEv
|
|
|
132
133
|
setVideoRecordingDefaults(width: number, height: number, fps: number): Promise<SettingsAckSuccessEvent>
|
|
133
134
|
setMaxVideoRecordingDuration(minutes: number): Promise<SettingsAckSuccessEvent>
|
|
134
135
|
setCameraFov(request: CameraFovRequest): Promise<CameraFovResult>
|
|
136
|
+
setLegacyCameraFov(request: CameraFovRequest): Promise<CameraFovResult>
|
|
137
|
+
restoreLegacyCameraFov(): Promise<void>
|
|
138
|
+
setCameraFovOverride(request: CameraFovOverrideRequest): Promise<CameraFovResult>
|
|
139
|
+
releaseCameraFovOverride(leaseId: string): Promise<SettingsAckSuccessEvent>
|
|
135
140
|
/**
|
|
136
141
|
* Configure camera HAL tuning (ANR / gain) on Mentra Live glasses.
|
|
137
142
|
* Sends a {@code camera_tuning_config} BLE message; the ASG client relays it as a
|
|
@@ -144,6 +149,7 @@ declare class BluetoothSdkNativeModule extends NativeModule<BluetoothSdkModuleEv
|
|
|
144
149
|
queryGalleryStatus(): Promise<GalleryStatusEvent>
|
|
145
150
|
requestPhoto(params: PhotoRequestParams): Promise<PhotoSuccessResponseEvent>
|
|
146
151
|
warmUpCamera(params: WarmUpCameraParams): Promise<CameraStatusEvent>
|
|
152
|
+
stopCameraWarmUp(requestId: string): Promise<void>
|
|
147
153
|
|
|
148
154
|
// OTA Commands
|
|
149
155
|
setOtaVersionUrl(otaVersionUrl: string): void
|
|
@@ -184,6 +190,23 @@ declare class BluetoothSdkNativeModule extends NativeModule<BluetoothSdkModuleEv
|
|
|
184
190
|
// Used to suspend LC3 mic during audio playback to avoid MCU overload
|
|
185
191
|
setOwnAppAudioPlaying(playing: boolean): Promise<void>
|
|
186
192
|
|
|
193
|
+
// Live PCM output stream (miniapp speaker.createStream). MentraOS-internal
|
|
194
|
+
// — 16-bit LE PCM chunks into a streaming AudioTrack (USAGE_MEDIA, so it
|
|
195
|
+
// follows the phone's media route, e.g. A2DP to glasses). Implemented with
|
|
196
|
+
// AudioTrack on Android and AVAudioEngine on iOS.
|
|
197
|
+
/** Open a PCM stream session. One AudioTrack per id; caller manages ids. */
|
|
198
|
+
pcmStreamOpen(streamId: string, sampleRate: number, channels: number, volume: number): Promise<void>
|
|
199
|
+
/**
|
|
200
|
+
* Append base64 PCM. Resolves with the queued-but-unplayed backlog in ms;
|
|
201
|
+
* blocks (on a background dispatcher) while the backlog is above the
|
|
202
|
+
* backpressure ceiling, so awaited writes self-throttle to realtime.
|
|
203
|
+
*/
|
|
204
|
+
pcmStreamWrite(streamId: string, base64: string): Promise<{bufferedMs: number}>
|
|
205
|
+
/** Drain the backlog, then stop. Resolves with the total played duration. */
|
|
206
|
+
pcmStreamClose(streamId: string): Promise<{durationMs: number}>
|
|
207
|
+
/** Stop immediately, dropping any backlog. Idempotent. */
|
|
208
|
+
pcmStreamAbort(streamId: string): Promise<void>
|
|
209
|
+
|
|
187
210
|
/** Mentra Live only: K900 `cs_getvol` / `sr_getvol`. */
|
|
188
211
|
getGlassesMediaVolume(): Promise<GlassesMediaVolumeGetResult>
|
|
189
212
|
/** Mentra Live only: K900 `cs_vol` / `sr_vol`; level clamped 0–15 on native. */
|
|
@@ -244,10 +267,7 @@ const DEFAULT_CONNECT_OPTIONS: Required<ConnectOptions> = {
|
|
|
244
267
|
|
|
245
268
|
const DEFAULT_SCAN_TIMEOUT_MS = 15_000
|
|
246
269
|
|
|
247
|
-
function bindNativeMethod<T extends (...args: never[]) => unknown>(
|
|
248
|
-
module: Record<string, unknown>,
|
|
249
|
-
name: string,
|
|
250
|
-
): T {
|
|
270
|
+
function bindNativeMethod<T extends (...args: never[]) => unknown>(module: Record<string, unknown>, name: string): T {
|
|
251
271
|
const method = module[name]
|
|
252
272
|
if (typeof method !== "function") {
|
|
253
273
|
console.warn(`[BluetoothSdk] Native method "${name}" is unavailable — rebuild the app (bun android / bun ios)`)
|
|
@@ -286,11 +306,7 @@ function normalizeCameraFov(request: CameraFovRequest): CameraFovSetting {
|
|
|
286
306
|
const roiPosition = request.roiPosition ?? "center"
|
|
287
307
|
|
|
288
308
|
return {
|
|
289
|
-
fov: clampInteger(
|
|
290
|
-
Number.isFinite(request.fov) ? request.fov : CAMERA_FOV_DEFAULT,
|
|
291
|
-
CAMERA_FOV_MIN,
|
|
292
|
-
CAMERA_FOV_MAX,
|
|
293
|
-
),
|
|
309
|
+
fov: clampInteger(Number.isFinite(request.fov) ? request.fov : CAMERA_FOV_DEFAULT, CAMERA_FOV_MIN, CAMERA_FOV_MAX),
|
|
294
310
|
roiPosition: clampInteger(CAMERA_ROI_POSITION_VALUES[roiPosition] ?? 0, CAMERA_ROI_MIN, CAMERA_ROI_MAX) as
|
|
295
311
|
| 0
|
|
296
312
|
| 1
|
|
@@ -470,14 +486,49 @@ NativeBluetoothSdkModule.setVoiceActivityDetectionEnabled = function (enabled: b
|
|
|
470
486
|
return this.updateBluetoothSettings({voice_activity_detection_enabled: enabled})
|
|
471
487
|
}
|
|
472
488
|
|
|
473
|
-
const nativeSetCameraFov = bindNativeMethod<
|
|
474
|
-
|
|
475
|
-
|
|
489
|
+
const nativeSetCameraFov = bindNativeMethod<(fov: CameraFovSetting) => MaybePromise<CameraFovResult>>(
|
|
490
|
+
NativeBluetoothSdkModule as unknown as Record<string, unknown>,
|
|
491
|
+
"setCameraFov",
|
|
492
|
+
)
|
|
476
493
|
NativeBluetoothSdkModule.setCameraFov = function (request: CameraFovRequest) {
|
|
477
494
|
const setting = normalizeCameraFov(request)
|
|
478
495
|
return Promise.resolve(nativeSetCameraFov(setting))
|
|
479
496
|
}
|
|
480
497
|
|
|
498
|
+
const nativeSetLegacyCameraFov = bindNativeMethod<(fov: CameraFovSetting) => MaybePromise<CameraFovResult>>(
|
|
499
|
+
NativeBluetoothSdkModule as unknown as Record<string, unknown>,
|
|
500
|
+
"setLegacyCameraFov",
|
|
501
|
+
)
|
|
502
|
+
NativeBluetoothSdkModule.setLegacyCameraFov = function (request: CameraFovRequest) {
|
|
503
|
+
return Promise.resolve(nativeSetLegacyCameraFov(normalizeCameraFov(request)))
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
const nativeRestoreLegacyCameraFov = bindNativeMethod<() => MaybePromise<void>>(
|
|
507
|
+
NativeBluetoothSdkModule as unknown as Record<string, unknown>,
|
|
508
|
+
"restoreLegacyCameraFov",
|
|
509
|
+
)
|
|
510
|
+
NativeBluetoothSdkModule.restoreLegacyCameraFov = function () {
|
|
511
|
+
return Promise.resolve(nativeRestoreLegacyCameraFov())
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
const nativeSetCameraFovOverride = bindNativeMethod<
|
|
515
|
+
(request: CameraFovSetting & {leaseId: string; ttlMs: number}) => MaybePromise<CameraFovResult>
|
|
516
|
+
>(NativeBluetoothSdkModule as unknown as Record<string, unknown>, "setCameraFovOverride")
|
|
517
|
+
NativeBluetoothSdkModule.setCameraFovOverride = function (request: CameraFovOverrideRequest) {
|
|
518
|
+
const setting = normalizeCameraFov(request)
|
|
519
|
+
const requestedTtl =
|
|
520
|
+
typeof request.ttlMs === "number" && Number.isFinite(request.ttlMs) && request.ttlMs > 0
|
|
521
|
+
? Math.round(request.ttlMs)
|
|
522
|
+
: 300_000
|
|
523
|
+
return Promise.resolve(
|
|
524
|
+
nativeSetCameraFovOverride({
|
|
525
|
+
...setting,
|
|
526
|
+
leaseId: request.leaseId,
|
|
527
|
+
ttlMs: Math.min(requestedTtl, 600_000),
|
|
528
|
+
}),
|
|
529
|
+
)
|
|
530
|
+
}
|
|
531
|
+
|
|
481
532
|
NativeBluetoothSdkModule.setMicState = function (
|
|
482
533
|
enabled: boolean,
|
|
483
534
|
useGlassesMic?: boolean,
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import {NativeModule, requireOptionalNativeModule} from "expo"
|
|
2
|
+
|
|
3
|
+
export type LocalNetworkResponse = {
|
|
4
|
+
status: number
|
|
5
|
+
headers: Record<string, string>
|
|
6
|
+
bodyBase64: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type LocalNetworkDownloadResult = {
|
|
10
|
+
statusCode: number
|
|
11
|
+
bytesWritten: number
|
|
12
|
+
headers: Record<string, string>
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type LocalNetworkDownloadProgress = {
|
|
16
|
+
requestId: string
|
|
17
|
+
bytesWritten: number
|
|
18
|
+
contentLength: number
|
|
19
|
+
statusCode?: number
|
|
20
|
+
headers?: Record<string, string>
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
type LocalNetworkEvents = {
|
|
24
|
+
downloadProgress: (event: LocalNetworkDownloadProgress) => void
|
|
25
|
+
networkLost: (event: {ssid: string}) => void
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
declare class MentraLocalNetworkNativeModule extends NativeModule<LocalNetworkEvents> {
|
|
29
|
+
connect(ssid: string, password: string): Promise<{connected: boolean; ssid: string}>
|
|
30
|
+
request(
|
|
31
|
+
requestId: string,
|
|
32
|
+
url: string,
|
|
33
|
+
method: string,
|
|
34
|
+
headers: Record<string, string>,
|
|
35
|
+
body: string | null,
|
|
36
|
+
timeoutMs: number,
|
|
37
|
+
): Promise<LocalNetworkResponse>
|
|
38
|
+
download(
|
|
39
|
+
requestId: string,
|
|
40
|
+
url: string,
|
|
41
|
+
destination: string,
|
|
42
|
+
headers: Record<string, string>,
|
|
43
|
+
connectionTimeoutMs: number,
|
|
44
|
+
readTimeoutMs: number,
|
|
45
|
+
): Promise<LocalNetworkDownloadResult>
|
|
46
|
+
cancel(requestId: string): Promise<void>
|
|
47
|
+
disconnect(): Promise<void>
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export default requireOptionalNativeModule<MentraLocalNetworkNativeModule>("MentraLocalNetwork")
|
|
@@ -12,6 +12,7 @@ export function warmUpCameraParamsForNative(
|
|
|
12
12
|
): Record<string, string | number> {
|
|
13
13
|
const payload: Record<string, string | number> = {
|
|
14
14
|
size: normalizePhotoSizeTier(params.size),
|
|
15
|
+
mode: params.mode ?? "photo",
|
|
15
16
|
}
|
|
16
17
|
const requestId = nonBlankString(params.requestId)
|
|
17
18
|
if (requestId != null) {
|
|
@@ -25,12 +25,11 @@ function nonBlankString(value?: string | null): string | undefined {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
/** Expo Android bridge rejects null values in Map<String, Any> — omit optional nullish fields. */
|
|
28
|
-
export function photoRequestParamsForNative(
|
|
29
|
-
params: PhotoRequestParams,
|
|
30
|
-
): Record<string, string | number | boolean> {
|
|
28
|
+
export function photoRequestParamsForNative(params: PhotoRequestParams): Record<string, string | number | boolean> {
|
|
31
29
|
const payload: Record<string, string | number | boolean> = {
|
|
32
30
|
size: normalizePhotoSizeTier(params.size),
|
|
33
31
|
mode: params.mode ?? "photo",
|
|
32
|
+
transferMethod: params.transferMethod ?? "auto",
|
|
34
33
|
webhookUrl: params.webhookUrl ?? "",
|
|
35
34
|
compress: params.compress,
|
|
36
35
|
sound: params.sound,
|
package/src/index.ts
CHANGED
|
@@ -113,10 +113,14 @@ export const BluetoothSdk: BluetoothSdkPublicModule = Object.freeze({
|
|
|
113
113
|
},
|
|
114
114
|
setMaxVideoRecordingDuration: bindPublicMethod("setMaxVideoRecordingDuration"),
|
|
115
115
|
setCameraFov: bindPublicMethod("setCameraFov"),
|
|
116
|
+
setLegacyCameraFov: bindPublicMethod("setLegacyCameraFov"),
|
|
117
|
+
setCameraFovOverride: bindPublicMethod("setCameraFovOverride"),
|
|
118
|
+
releaseCameraFovOverride: bindPublicMethod("releaseCameraFovOverride"),
|
|
116
119
|
setCameraTuningConfig: bindPublicMethod("setCameraTuningConfig"),
|
|
117
120
|
queryGalleryStatus: bindPublicMethod("queryGalleryStatus"),
|
|
118
121
|
requestPhoto: bindPublicMethod("requestPhoto"),
|
|
119
122
|
warmUpCamera: bindPublicMethod("warmUpCamera"),
|
|
123
|
+
stopCameraWarmUp: bindPublicMethod("stopCameraWarmUp"),
|
|
120
124
|
startVideoRecording: bindPublicMethod("startVideoRecording"),
|
|
121
125
|
stopVideoRecording: bindPublicMethod("stopVideoRecording"),
|
|
122
126
|
startStream: bindPublicMethod("startStream"),
|