@mentra/bluetooth-sdk 0.1.10 → 0.1.12

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.
Files changed (53) hide show
  1. package/README.md +52 -3
  2. package/android/build.gradle +1 -0
  3. package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkAnalytics.kt +182 -0
  4. package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +61 -19
  5. package/android/src/main/java/com/mentra/bluetoothsdk/Bridge.kt +24 -0
  6. package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +32 -8
  7. package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +8 -0
  8. package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +76 -5
  9. package/android/src/main/java/com/mentra/bluetoothsdk/camera/CameraModels.kt +17 -1
  10. package/android/src/main/java/com/mentra/bluetoothsdk/events/BluetoothEvents.kt +1 -0
  11. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G2.kt +829 -643
  12. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.java +34 -5
  13. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraNex.kt +40 -64
  14. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/SGCManager.kt +23 -1
  15. package/android/src/main/java/com/mentra/bluetoothsdk/types/DeviceModels.kt +2 -1
  16. package/build/BluetoothSdk.types.d.ts +45 -2
  17. package/build/BluetoothSdk.types.d.ts.map +1 -1
  18. package/build/BluetoothSdk.types.js.map +1 -1
  19. package/build/_private/BluetoothSdkModule.d.ts +2 -1
  20. package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
  21. package/build/_private/BluetoothSdkModule.js +14 -1
  22. package/build/_private/BluetoothSdkModule.js.map +1 -1
  23. package/build/index.d.ts +1 -1
  24. package/build/index.d.ts.map +1 -1
  25. package/build/index.js +4 -0
  26. package/build/index.js.map +1 -1
  27. package/ios/BluetoothSdkModule.swift +24 -7
  28. package/ios/Source/BluetoothSdkDefaults.swift +27 -0
  29. package/ios/Source/Bridge.swift +16 -0
  30. package/ios/Source/DeviceManager.swift +33 -27
  31. package/ios/Source/DeviceStore.swift +8 -2
  32. package/ios/Source/MentraBluetoothSDK.swift +160 -8
  33. package/ios/Source/camera/CameraModels.swift +50 -1
  34. package/ios/Source/events/BluetoothEvents.swift +3 -0
  35. package/ios/Source/internal/BluetoothAvailability.swift +20 -0
  36. package/ios/Source/internal/BluetoothSdkAnalytics.swift +160 -0
  37. package/ios/Source/sgcs/G1.swift +3 -3
  38. package/ios/Source/sgcs/G2.swift +1022 -640
  39. package/ios/Source/sgcs/Mach1.swift +2 -2
  40. package/ios/Source/sgcs/MentraLive.swift +31 -7
  41. package/ios/Source/sgcs/MentraNex.swift +215 -85
  42. package/ios/Source/sgcs/SGCManager.swift +26 -6
  43. package/ios/Source/sgcs/Simulated.swift +2 -2
  44. package/ios/Source/types/DeviceModels.swift +5 -1
  45. package/package.json +1 -1
  46. package/plugin/build/index.d.ts +4 -0
  47. package/plugin/build/withAndroid.d.ts +2 -3
  48. package/plugin/build/withAndroid.js +46 -0
  49. package/plugin/build/withIos.d.ts +2 -3
  50. package/plugin/build/withIos.js +32 -0
  51. package/src/BluetoothSdk.types.ts +53 -2
  52. package/src/_private/BluetoothSdkModule.ts +27 -4
  53. package/src/index.ts +5 -0
@@ -31,9 +31,16 @@ protocol SGCManager {
31
31
  /// Defaulted in an extension to delegate to the basic recording path; devices
32
32
  /// that support custom settings (e.g. Mentra Live) override this.
33
33
  func startVideoRecording(
34
- requestId: String, save: Bool, flash: Bool, sound: Bool, width: Int, height: Int, fps: Int
34
+ requestId: String, save: Bool, flash: Bool, sound: Bool, width: Int, height: Int, fps: Int,
35
+ maxRecordingTimeMinutes: Int
35
36
  )
36
37
  func stopVideoRecording(requestId: String)
38
+ /// Stop recording and upload the result to `webhookUrl` (multipart) using
39
+ /// `authToken`. Supplied at stop time so the token is fresh when the upload
40
+ /// runs. Defaulted in an extension to ignore the upload target and just stop;
41
+ /// devices that support webhook upload (e.g. Mentra Live) override this. An
42
+ /// empty/nil `webhookUrl` means "keep the video on device".
43
+ func stopVideoRecording(requestId: String, webhookUrl: String?, authToken: String?)
37
44
 
38
45
  // MARK: - Button Settings
39
46
 
@@ -47,8 +54,8 @@ protocol SGCManager {
47
54
 
48
55
  func setBrightness(_ level: Int, autoMode: Bool)
49
56
  func clearDisplay()
50
- func sendTextWall(_ text: String)
51
- func sendDoubleTextWall(_ top: String, _ bottom: String)
57
+ func sendTextWall(_ text: String) async
58
+ func sendDoubleTextWall(_ top: String, _ bottom: String) async
52
59
  /// Display a bitmap. Optional `x`/`y`/`width`/`height` position and size the target
53
60
  /// container (used by G2; other SGCs ignore positioning and render the bitmap as before).
54
61
  func displayBitmap(base64ImageData: String, x: Int32?, y: Int32?, width: Int32?, height: Int32?) async -> Bool
@@ -64,7 +71,7 @@ protocol SGCManager {
64
71
 
65
72
  // MARK: - Notification Panel
66
73
 
67
- func showNotificationsPanel()
74
+ func showNotificationsPanel() async
68
75
 
69
76
  // MARK: - Calendar Events
70
77
 
@@ -77,6 +84,9 @@ protocol SGCManager {
77
84
  // MARK: - Device Control
78
85
 
79
86
  func setHeadUpAngle(_ angle: Int)
87
+ /// Enable/disable raw accelerometer (IMU) reporting from the glasses.
88
+ /// Default no-op; only G2 streams IMU data today.
89
+ func setImuEnabled(_ enabled: Bool) async
80
90
  func getBatteryStatus()
81
91
  func setSilentMode(_ enabled: Bool)
82
92
  func exit()
@@ -142,11 +152,15 @@ extension SGCManager {
142
152
 
143
153
  func startVideoRecording(
144
154
  requestId: String, save: Bool, flash: Bool, sound: Bool, width _: Int, height _: Int,
145
- fps _: Int
155
+ fps _: Int, maxRecordingTimeMinutes _: Int
146
156
  ) {
147
157
  startVideoRecording(requestId: requestId, save: save, flash: flash, sound: sound)
148
158
  }
149
159
 
160
+ func stopVideoRecording(requestId: String, webhookUrl _: String?, authToken _: String?) {
161
+ stopVideoRecording(requestId: requestId)
162
+ }
163
+
150
164
  // MARK: - Dashboard (default: combined wire format; Nex implements single-field)
151
165
 
152
166
  func setDashboardHeightOnly(_ height: Int) {
@@ -165,7 +179,13 @@ extension SGCManager {
165
179
 
166
180
  // MARK: - Notification Panel (default no-op — only G2 supports this)
167
181
 
168
- func showNotificationsPanel() {}
182
+ func showNotificationsPanel() async {}
183
+
184
+ // MARK: - IMU (default no-op — only G2 streams accelerometer data)
185
+
186
+ func setImuEnabled(_: Bool) async {
187
+ Bridge.log("SGC: setImuEnabled not supported")
188
+ }
169
189
 
170
190
  // MARK: - Calendar Events (default no-op — only G2 supports this)
171
191
 
@@ -133,11 +133,11 @@ class Simulated: SGCManager {
133
133
  Bridge.log("clearDisplay")
134
134
  }
135
135
 
136
- func sendTextWall(_: String) {
136
+ func sendTextWall(_: String) async {
137
137
  Bridge.log("sendTextWall")
138
138
  }
139
139
 
140
- func sendDoubleTextWall(_: String, _: String) {
140
+ func sendDoubleTextWall(_: String, _: String) async {
141
141
  Bridge.log("sendDoubleTextWall")
142
142
  }
143
143
 
@@ -3,7 +3,11 @@ import Foundation
3
3
  public struct MentraBluetoothSDKConfiguration {
4
4
  public static let `default` = MentraBluetoothSDKConfiguration()
5
5
 
6
- public init() {}
6
+ public let analytics: BluetoothSdkAnalyticsConfiguration
7
+
8
+ public init(analytics: BluetoothSdkAnalyticsConfiguration = BluetoothSdkAnalyticsConfiguration()) {
9
+ self.analytics = analytics
10
+ }
7
11
  }
8
12
 
9
13
  public enum DeviceModel: String {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mentra/bluetooth-sdk",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "description": "SDK for communicating with smart glasses",
5
5
  "main": "build/index.js",
6
6
  "react-native": "src/index.ts",
@@ -1,6 +1,10 @@
1
1
  import { type ConfigPlugin } from "expo/config-plugins";
2
2
  export interface BluetoothSdkPluginProps {
3
3
  node?: boolean;
4
+ analytics?: boolean | BluetoothSdkAnalyticsPluginProps;
5
+ }
6
+ export interface BluetoothSdkAnalyticsPluginProps {
7
+ enabled?: boolean;
4
8
  }
5
9
  declare const withBluetoothSdk: ConfigPlugin<BluetoothSdkPluginProps>;
6
10
  export default withBluetoothSdk;
@@ -1,4 +1,3 @@
1
1
  import { type ConfigPlugin } from "expo/config-plugins";
2
- export declare const withAndroidConfiguration: ConfigPlugin<{
3
- node?: boolean;
4
- }>;
2
+ import { type BluetoothSdkPluginProps } from "./index";
3
+ export declare const withAndroidConfiguration: ConfigPlugin<BluetoothSdkPluginProps>;
@@ -7,6 +7,10 @@ exports.withAndroidConfiguration = void 0;
7
7
  const child_process_1 = require("child_process");
8
8
  const path_1 = __importDefault(require("path"));
9
9
  const config_plugins_1 = require("expo/config-plugins");
10
+ const META_ANALYTICS_DISABLED = "com.mentra.bluetoothsdk.analytics.disabled";
11
+ const STALE_META_POSTHOG_API_KEY = "com.mentra.bluetoothsdk.analytics.posthog_api_key";
12
+ const STALE_META_POSTHOG_HOST = "com.mentra.bluetoothsdk.analytics.posthog_host";
13
+ const ANALYTICS_META_NAMES = [META_ANALYTICS_DISABLED, STALE_META_POSTHOG_API_KEY, STALE_META_POSTHOG_HOST];
10
14
  function getBluetoothSdkRoot() {
11
15
  return path_1.default.dirname(require.resolve("../../package.json"));
12
16
  }
@@ -133,9 +137,51 @@ function withSherpaOnnxLocalMavenRepo(config) {
133
137
  return config;
134
138
  });
135
139
  }
140
+ function resolveAnalyticsProps(props) {
141
+ const analytics = props?.analytics;
142
+ let disabled;
143
+ if (analytics === false) {
144
+ disabled = true;
145
+ }
146
+ else if (analytics === true) {
147
+ disabled = false;
148
+ }
149
+ else if (typeof analytics === "object" && analytics.enabled !== undefined) {
150
+ disabled = !analytics.enabled;
151
+ }
152
+ return {
153
+ disabled,
154
+ };
155
+ }
156
+ function upsertMetaData(application, name, value) {
157
+ application["meta-data"] ??= [];
158
+ const existing = application["meta-data"].find((item) => item.$?.["android:name"] === name);
159
+ const entry = existing ?? { $: { "android:name": name } };
160
+ entry.$["android:value"] = value;
161
+ if (!existing) {
162
+ application["meta-data"].push(entry);
163
+ }
164
+ }
165
+ function removeMetaData(application, name) {
166
+ application["meta-data"] = (application["meta-data"] ?? []).filter((item) => item.$?.["android:name"] !== name);
167
+ }
168
+ function withAnalyticsManifestMetadata(config, props) {
169
+ return (0, config_plugins_1.withAndroidManifest)(config, (config) => {
170
+ const analytics = resolveAnalyticsProps(props);
171
+ const application = config_plugins_1.AndroidConfig.Manifest.getMainApplicationOrThrow(config.modResults);
172
+ for (const name of ANALYTICS_META_NAMES) {
173
+ removeMetaData(application, name);
174
+ }
175
+ if (analytics.disabled !== undefined) {
176
+ upsertMetaData(application, META_ANALYTICS_DISABLED, analytics.disabled ? "true" : "false");
177
+ }
178
+ return config;
179
+ });
180
+ }
136
181
  const withAndroidConfiguration = (config, props) => {
137
182
  config = withSettingsGradleModifications(config);
138
183
  config = withSherpaOnnxLocalMavenRepo(config);
184
+ config = withAnalyticsManifestMetadata(config, props);
139
185
  if (props?.node) {
140
186
  config = withGradlePropertiesModifications(config);
141
187
  }
@@ -1,4 +1,3 @@
1
1
  import { type ConfigPlugin } from "expo/config-plugins";
2
- export declare const withIosConfiguration: ConfigPlugin<{
3
- node?: boolean;
4
- }>;
2
+ import { type BluetoothSdkPluginProps } from "./index";
3
+ export declare const withIosConfiguration: ConfigPlugin<BluetoothSdkPluginProps>;
@@ -10,6 +10,9 @@ const path_1 = __importDefault(require("path"));
10
10
  const config_plugins_1 = require("expo/config-plugins");
11
11
  const BLUETOOTH_SDK_EXPO_ADAPTER_ENV = "MENTRA_BLUETOOTH_SDK_INCLUDE_EXPO_ADAPTER";
12
12
  const BLUETOOTH_SDK_EXPO_ADAPTER_LINE = `ENV['${BLUETOOTH_SDK_EXPO_ADAPTER_ENV}'] ||= '1'`;
13
+ const INFO_ANALYTICS_DISABLED = "MentraBluetoothSdkAnalyticsDisabled";
14
+ const STALE_INFO_POSTHOG_API_KEY = "MentraBluetoothSdkPostHogApiKey";
15
+ const STALE_INFO_POSTHOG_HOST = "MentraBluetoothSdkPostHogHost";
13
16
  const ensureBluetoothSdkExpoAdapterPodEnv = (podfile) => {
14
17
  if (podfile.includes(BLUETOOTH_SDK_EXPO_ADAPTER_ENV)) {
15
18
  return podfile;
@@ -51,11 +54,40 @@ const withXcodeEnvLocal = (config) => {
51
54
  },
52
55
  ]);
53
56
  };
57
+ function resolveAnalyticsProps(props) {
58
+ const analytics = props?.analytics;
59
+ let disabled;
60
+ if (analytics === false) {
61
+ disabled = true;
62
+ }
63
+ else if (analytics === true) {
64
+ disabled = false;
65
+ }
66
+ else if (typeof analytics === "object" && analytics.enabled !== undefined) {
67
+ disabled = !analytics.enabled;
68
+ }
69
+ return {
70
+ disabled,
71
+ };
72
+ }
73
+ function withAnalyticsInfoPlist(config, props) {
74
+ return (0, config_plugins_1.withInfoPlist)(config, (config) => {
75
+ const analytics = resolveAnalyticsProps(props);
76
+ delete config.modResults[INFO_ANALYTICS_DISABLED];
77
+ delete config.modResults[STALE_INFO_POSTHOG_API_KEY];
78
+ delete config.modResults[STALE_INFO_POSTHOG_HOST];
79
+ if (analytics.disabled !== undefined) {
80
+ config.modResults[INFO_ANALYTICS_DISABLED] = analytics.disabled;
81
+ }
82
+ return config;
83
+ });
84
+ }
54
85
  const withIosConfiguration = (config, props) => {
55
86
  config = (0, config_plugins_1.withPodfile)(config, (config) => {
56
87
  config.modResults.contents = ensureBluetoothSdkExpoAdapterPodEnv(config.modResults.contents);
57
88
  return config;
58
89
  });
90
+ config = withAnalyticsInfoPlist(config, props);
59
91
  if (props?.node) {
60
92
  config = withXcodeEnvLocal(config);
61
93
  }
@@ -21,6 +21,14 @@ export type TouchEvent = {
21
21
  timestamp: number
22
22
  }
23
23
 
24
+ export type AccelEvent = {
25
+ type: "accel_event"
26
+ x: number
27
+ y: number
28
+ z: number
29
+ timestamp: number
30
+ }
31
+
24
32
  export type HeadUpEvent = {
25
33
  up: boolean
26
34
  }
@@ -299,6 +307,24 @@ export type VideoRecordingSuccessStatusEvent =
299
307
  | VideoRecordingStartedStatusEvent
300
308
  | VideoRecordingStoppedStatusEvent
301
309
 
310
+ export type MediaUploadSuccessEvent = {
311
+ type: "media_success"
312
+ requestId: string
313
+ mediaUrl: string
314
+ mediaType: number
315
+ timestamp: number
316
+ }
317
+
318
+ export type MediaUploadErrorEvent = {
319
+ type: "media_error"
320
+ requestId: string
321
+ errorMessage: string
322
+ mediaType: number
323
+ timestamp: number
324
+ }
325
+
326
+ export type MediaUploadEvent = MediaUploadSuccessEvent | MediaUploadErrorEvent
327
+
302
328
  export type GalleryStatusEvent = {
303
329
  type: "gallery_status"
304
330
  photos: number
@@ -396,7 +422,7 @@ export type SettingsAckSuccessEvent = Omit<SettingsAckEvent, "status"> & {
396
422
  export type RgbLedAction = "on" | "off"
397
423
  export type RgbLedColor = "red" | "green" | "blue" | "orange" | "white"
398
424
  export type PhotoSize = "small" | "medium" | "large" | "full"
399
- export type ButtonPhotoSize = "small" | "medium" | "large"
425
+ export type ButtonPhotoSize = "small" | "medium" | "large" | "max"
400
426
  export type PhotoCompression = "none" | "medium" | "heavy"
401
427
 
402
428
  /**
@@ -408,6 +434,12 @@ export interface VideoRecordingSettings {
408
434
  width?: number
409
435
  height?: number
410
436
  fps?: number
437
+ /**
438
+ * Optional auto-stop timer in minutes, sent on `start_video_recording`.
439
+ * `0` (the default) means record until stopped or interrupted
440
+ * (battery/storage/thermal/error).
441
+ */
442
+ maxRecordingTimeMinutes?: number
411
443
  }
412
444
  export const DeviceModels = {
413
445
  Simulated: "Simulated Glasses",
@@ -722,6 +754,7 @@ export type BluetoothSdkModuleEvents = {
722
754
  glasses_not_ready: (event: GlassesNotReadyEvent) => void
723
755
  button_press: (event: ButtonPressEvent) => void
724
756
  touch_event: (event: TouchEvent) => void
757
+ accel_event: (event: AccelEvent) => void
725
758
  head_up: (event: HeadUpEvent) => void
726
759
  voice_activity_detection_status: (event: VoiceActivityDetectionStatusEvent) => void
727
760
  speaking_status: (event: SpeakingStatusEvent) => void
@@ -734,6 +767,8 @@ export type BluetoothSdkModuleEvents = {
734
767
  photo_response: (event: PhotoResponseEvent) => void
735
768
  photo_status: (event: PhotoStatusEvent) => void
736
769
  video_recording_status: (event: VideoRecordingStatusEvent) => void
770
+ media_success: (event: MediaUploadSuccessEvent) => void
771
+ media_error: (event: MediaUploadErrorEvent) => void
737
772
  gallery_status: (event: GalleryStatusEvent) => void
738
773
  compatible_glasses_search_stop: (event: CompatibleGlassesSearchStopEvent) => void
739
774
  heartbeat_sent: (event: HeartbeatSentEvent) => void
@@ -796,6 +831,7 @@ export type BluetoothSdkEventMap = {
796
831
  glasses_not_ready: GlassesNotReadyEvent
797
832
  button_press: ButtonPressEvent
798
833
  touch_event: TouchEvent
834
+ accel_event: AccelEvent
799
835
  head_up: HeadUpEvent
800
836
  voice_activity_detection_status: VoiceActivityDetectionStatusEvent
801
837
  speaking_status: SpeakingStatusEvent
@@ -808,6 +844,8 @@ export type BluetoothSdkEventMap = {
808
844
  photo_response: PhotoResponseEvent
809
845
  photo_status: PhotoStatusEvent
810
846
  video_recording_status: VideoRecordingStatusEvent
847
+ media_success: MediaUploadSuccessEvent
848
+ media_error: MediaUploadErrorEvent
811
849
  gallery_status: GalleryStatusEvent
812
850
  compatible_glasses_search_stop: CompatibleGlassesSearchStopEvent
813
851
  swipe_volume_status: SwipeVolumeStatusEvent
@@ -865,6 +903,7 @@ export interface BluetoothSdkPublicModule {
865
903
  showDashboard(): Promise<void>
866
904
  setDashboardPosition(height: number, depth: number): Promise<void>
867
905
  setHeadUpAngle(angleDegrees: number): Promise<void>
906
+ setImuEnabled(enabled: boolean): Promise<void>
868
907
  setScreenDisabled(disabled: boolean): Promise<void>
869
908
 
870
909
  requestWifiScan(): Promise<WifiSearchResult[]>
@@ -887,7 +926,18 @@ export interface BluetoothSdkPublicModule {
887
926
  sound: boolean,
888
927
  settings?: VideoRecordingSettings,
889
928
  ): Promise<VideoRecordingStartedStatusEvent>
890
- stopVideoRecording(requestId: string): Promise<VideoRecordingStoppedStatusEvent>
929
+ /**
930
+ * Stop the active recording. When {@link webhookUrl} is provided, the glasses
931
+ * upload the recorded video to it (multipart) using {@link authToken}. These
932
+ * are supplied at stop time (not start) so the token is fresh when the upload
933
+ * runs — a recording can last arbitrarily long. An empty/omitted webhook keeps
934
+ * the video on device (no upload).
935
+ */
936
+ stopVideoRecording(
937
+ requestId: string,
938
+ webhookUrl?: string,
939
+ authToken?: string,
940
+ ): Promise<VideoRecordingStoppedStatusEvent>
891
941
 
892
942
  startStream(params: StreamStartRequest): Promise<StreamStatusEvent>
893
943
  stopStream(): Promise<StreamStatusEvent>
@@ -1140,6 +1190,7 @@ export type BluetoothSettingsUpdate = Partial<{
1140
1190
  screen_disabled: boolean
1141
1191
  contextual_dashboard: boolean
1142
1192
  head_up_angle: number
1193
+ imu_enabled: boolean
1143
1194
  brightness: number
1144
1195
  auto_brightness: boolean
1145
1196
  dashboard_height: number
@@ -100,6 +100,7 @@ declare class BluetoothSdkNativeModule extends NativeModule<BluetoothSdkModuleEv
100
100
  setDashboardMenu(items: DashboardMenuItem[]): Promise<void>
101
101
  setCalendarEvents(events: CalendarEvent[]): Promise<void>
102
102
  setHeadUpAngle(angleDegrees: number): Promise<void>
103
+ setImuEnabled(enabled: boolean): Promise<void>
103
104
  setScreenDisabled(disabled: boolean): Promise<void>
104
105
  ping(): Promise<void>
105
106
  dbg1(): Promise<void>
@@ -147,7 +148,11 @@ declare class BluetoothSdkNativeModule extends NativeModule<BluetoothSdkModuleEv
147
148
  sound: boolean,
148
149
  settings?: VideoRecordingSettings,
149
150
  ): Promise<VideoRecordingStartedStatusEvent>
150
- stopVideoRecording(requestId: string): Promise<VideoRecordingStoppedStatusEvent>
151
+ stopVideoRecording(
152
+ requestId: string,
153
+ webhookUrl?: string,
154
+ authToken?: string,
155
+ ): Promise<VideoRecordingStoppedStatusEvent>
151
156
 
152
157
  // Stream Commands
153
158
  startStream(params: StreamStartRequest): Promise<StreamStatusEvent>
@@ -225,6 +230,20 @@ const DEFAULT_CONNECT_OPTIONS: Required<ConnectOptions> = {
225
230
 
226
231
  const DEFAULT_SCAN_TIMEOUT_MS = 15_000
227
232
 
233
+ function bindNativeMethod<T extends (...args: never[]) => unknown>(
234
+ module: Record<string, unknown>,
235
+ name: string,
236
+ ): T {
237
+ const method = module[name]
238
+ if (typeof method !== "function") {
239
+ console.warn(`[BluetoothSdk] Native method "${name}" is unavailable — rebuild the app (bun android / bun ios)`)
240
+ return (async () => {
241
+ throw new Error(`BluetoothSdk.${name} is not available in this native build. Rebuild the app.`)
242
+ }) as T
243
+ }
244
+ return method.bind(module) as T
245
+ }
246
+
228
247
  const CAMERA_ROI_MIN = 0
229
248
  const CAMERA_ROI_MAX = 2
230
249
  const CAMERA_ROI_POSITION_VALUES: Record<CameraRoiPosition, CameraFovSetting["roiPosition"]> = {
@@ -425,6 +444,10 @@ NativeBluetoothSdkModule.setHeadUpAngle = function (angleDegrees: number) {
425
444
  return this.updateBluetoothSettings({head_up_angle: angleDegrees})
426
445
  }
427
446
 
447
+ NativeBluetoothSdkModule.setImuEnabled = function (enabled: boolean) {
448
+ return this.updateBluetoothSettings({imu_enabled: enabled})
449
+ }
450
+
428
451
  NativeBluetoothSdkModule.setScreenDisabled = function (disabled: boolean) {
429
452
  return this.updateBluetoothSettings({screen_disabled: disabled})
430
453
  }
@@ -433,9 +456,9 @@ NativeBluetoothSdkModule.setVoiceActivityDetectionEnabled = function (enabled: b
433
456
  return this.updateBluetoothSettings({voice_activity_detection_enabled: enabled})
434
457
  }
435
458
 
436
- const nativeSetCameraFov = NativeBluetoothSdkModule.setCameraFov.bind(NativeBluetoothSdkModule) as unknown as (
437
- fov: CameraFovSetting,
438
- ) => MaybePromise<CameraFovResult>
459
+ const nativeSetCameraFov = bindNativeMethod<
460
+ (fov: CameraFovSetting) => MaybePromise<CameraFovResult>
461
+ >(NativeBluetoothSdkModule as unknown as Record<string, unknown>, "setCameraFov")
439
462
  NativeBluetoothSdkModule.setCameraFov = function (request: CameraFovRequest) {
440
463
  const setting = normalizeCameraFov(request)
441
464
  return Promise.resolve(nativeSetCameraFov(setting))
package/src/index.ts CHANGED
@@ -8,6 +8,7 @@ const PUBLIC_EVENT_NAMES = new Set<BluetoothSdkEventName>([
8
8
  "glasses_not_ready",
9
9
  "button_press",
10
10
  "touch_event",
11
+ "accel_event",
11
12
  "head_up",
12
13
  "voice_activity_detection_status",
13
14
  "speaking_status",
@@ -20,6 +21,8 @@ const PUBLIC_EVENT_NAMES = new Set<BluetoothSdkEventName>([
20
21
  "photo_response",
21
22
  "photo_status",
22
23
  "video_recording_status",
24
+ "media_success",
25
+ "media_error",
23
26
  "gallery_status",
24
27
  "compatible_glasses_search_stop",
25
28
  "swipe_volume_status",
@@ -65,6 +68,7 @@ export const BluetoothSdk: BluetoothSdkPublicModule = Object.freeze({
65
68
  showDashboard: PrivateBluetoothSdkModule.showDashboard.bind(PrivateBluetoothSdkModule),
66
69
  setDashboardPosition: PrivateBluetoothSdkModule.setDashboardPosition.bind(PrivateBluetoothSdkModule),
67
70
  setHeadUpAngle: PrivateBluetoothSdkModule.setHeadUpAngle.bind(PrivateBluetoothSdkModule),
71
+ setImuEnabled: PrivateBluetoothSdkModule.setImuEnabled.bind(PrivateBluetoothSdkModule),
68
72
  setScreenDisabled: PrivateBluetoothSdkModule.setScreenDisabled.bind(PrivateBluetoothSdkModule),
69
73
  requestWifiScan: PrivateBluetoothSdkModule.requestWifiScan.bind(PrivateBluetoothSdkModule),
70
74
  sendWifiCredentials: PrivateBluetoothSdkModule.sendWifiCredentials.bind(PrivateBluetoothSdkModule),
@@ -124,6 +128,7 @@ export {
124
128
  } from "./BluetoothSdk.types"
125
129
 
126
130
  export type {
131
+ AccelEvent,
127
132
  AudioConnectedEvent,
128
133
  AudioDisconnectedEvent,
129
134
  AudioPairingNeededEvent,