@mentra/bluetooth-sdk 0.1.9 → 0.1.11

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 (147) hide show
  1. package/README.md +116 -18
  2. package/android/build.gradle +289 -16
  3. package/android/gradle.properties.example +9 -0
  4. package/android/lc3Lib/build.gradle +95 -6
  5. package/android/settings.gradle +3 -0
  6. package/android/silero/LICENSE.md +17 -0
  7. package/android/silero/build.gradle +42 -0
  8. package/android/silero/proguard-rules.pro +24 -0
  9. package/android/silero/src/androidTest/assets/hello.wav +0 -0
  10. package/android/silero/src/androidTest/java/com/konovalov/vad/silero/VadSileroTest.kt +79 -0
  11. package/android/silero/src/main/assets/LICENSE +21 -0
  12. package/android/silero/src/main/assets/silero_vad.onnx +0 -0
  13. package/android/silero/src/main/java/com/konovalov/vad/silero/Vad.kt +160 -0
  14. package/android/silero/src/main/java/com/konovalov/vad/silero/VadSilero.kt +449 -0
  15. package/android/silero/src/main/java/com/konovalov/vad/silero/config/FrameSize.kt +15 -0
  16. package/android/silero/src/main/java/com/konovalov/vad/silero/config/Mode.kt +14 -0
  17. package/android/silero/src/main/java/com/konovalov/vad/silero/config/SampleRate.kt +12 -0
  18. package/android/silero/src/main/java/com/konovalov/vad/silero/utils/AudioUtils.kt +47 -0
  19. package/android/silero/src/main/java/com/konovalov/vad/silero/utils/TensorMap.kt +41 -0
  20. package/android/silero/src/test/java/com/konovalov/vad/silero/utils/AudioUtilsTest.kt +44 -0
  21. package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkDefaults.kt +6 -0
  22. package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +195 -34
  23. package/android/src/main/java/com/mentra/bluetoothsdk/Bridge.kt +103 -21
  24. package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +210 -18
  25. package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +30 -2
  26. package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +787 -85
  27. package/android/src/main/java/com/mentra/bluetoothsdk/audio/AudioModels.kt +6 -2
  28. package/android/src/main/java/com/mentra/bluetoothsdk/camera/CameraModels.kt +125 -11
  29. package/android/src/main/java/com/mentra/bluetoothsdk/controllers/ControllerManager.kt +12 -2
  30. package/android/src/main/java/com/mentra/bluetoothsdk/controllers/R1.kt +8 -2
  31. package/android/src/main/java/com/mentra/bluetoothsdk/debug/BleTraceLogger.kt +33 -17
  32. package/android/src/main/java/com/mentra/bluetoothsdk/events/BluetoothEvents.kt +101 -0
  33. package/android/src/main/java/com/mentra/bluetoothsdk/internal/MapParsing.kt +8 -0
  34. package/android/src/main/java/com/mentra/bluetoothsdk/photoreceiver/LocalPhotoReceiverRegistry.java +108 -0
  35. package/android/src/main/java/com/mentra/bluetoothsdk/photoreceiver/LocalPhotoUploadServer.kt +399 -74
  36. package/android/src/main/java/com/mentra/bluetoothsdk/photoreceiver/MentraPhotoReceiverModule.kt +100 -17
  37. package/android/src/main/java/com/mentra/bluetoothsdk/requests/DisplayRequests.kt +17 -0
  38. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G1.java +2 -2
  39. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G2.kt +1293 -758
  40. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Mach1.java +2 -2
  41. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.java +271 -66
  42. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraNex.kt +197 -149
  43. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraosBle.java +12753 -27036
  44. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/SGCManager.kt +64 -2
  45. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Simulated.kt +16 -4
  46. package/android/src/main/java/com/mentra/bluetoothsdk/status/DeviceStatus.kt +60 -3
  47. package/android/src/main/java/com/mentra/bluetoothsdk/streaming/StreamModels.kt +12 -3
  48. package/android/src/main/java/com/mentra/bluetoothsdk/stt/STTTools.kt +87 -36
  49. package/android/src/main/java/com/mentra/bluetoothsdk/stt/SherpaOnnxTranscriber.kt +13 -4
  50. package/android/src/main/java/com/mentra/bluetoothsdk/stt/VadGateSpeechPolicy.kt +229 -0
  51. package/android/src/main/java/com/mentra/bluetoothsdk/tts/TTSTools.kt +180 -0
  52. package/android/src/main/java/com/mentra/bluetoothsdk/utils/AvifExifStripper.java +573 -0
  53. package/android/src/main/java/com/mentra/bluetoothsdk/utils/BlePhotoUploadService.java +488 -50
  54. package/android/src/main/java/com/mentra/bluetoothsdk/utils/HeifExifTagReader.java +199 -0
  55. package/android/src/main/java/com/mentra/bluetoothsdk/utils/MessageChunkReassembler.java +18 -1
  56. package/android/src/main/java/com/mentra/bluetoothsdk/utils/MessageChunker.java +64 -18
  57. package/android/src/main/java/com/mentra/bluetoothsdk/utils/NexSGCUtils.kt +48 -39
  58. package/android/src/test/java/com/mentra/bluetoothsdk/camera/PhotoRequestTest.kt +23 -0
  59. package/android/src/test/java/com/mentra/bluetoothsdk/photoreceiver/LocalPhotoReceiverRegistryTest.java +75 -0
  60. package/android/src/test/java/com/mentra/bluetoothsdk/utils/AvifExifStripperTest.java +30 -0
  61. package/android/src/test/java/com/mentra/bluetoothsdk/utils/BlePhotoUploadServiceTest.java +98 -0
  62. package/android/src/test/resources/avif_with_exif.avif +0 -0
  63. package/build/BluetoothSdk.types.d.ts +253 -44
  64. package/build/BluetoothSdk.types.d.ts.map +1 -1
  65. package/build/BluetoothSdk.types.js +11 -10
  66. package/build/BluetoothSdk.types.js.map +1 -1
  67. package/build/_private/BluetoothSdkModule.d.ts +37 -23
  68. package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
  69. package/build/_private/BluetoothSdkModule.js +43 -52
  70. package/build/_private/BluetoothSdkModule.js.map +1 -1
  71. package/build/_private/photoRequestPayload.d.ts +4 -0
  72. package/build/_private/photoRequestPayload.d.ts.map +1 -0
  73. package/build/_private/photoRequestPayload.js +25 -0
  74. package/build/_private/photoRequestPayload.js.map +1 -0
  75. package/build/index.d.ts +1 -1
  76. package/build/index.d.ts.map +1 -1
  77. package/build/index.js +25 -0
  78. package/build/index.js.map +1 -1
  79. package/build/react/useMentraBluetooth.d.ts +2 -2
  80. package/build/react/useMentraBluetooth.d.ts.map +1 -1
  81. package/build/react/useMentraBluetooth.js +3 -3
  82. package/build/react/useMentraBluetooth.js.map +1 -1
  83. package/ios/BluetoothSdkModule.swift +177 -70
  84. package/ios/LocalPhotoUploadServer.swift +370 -105
  85. package/ios/MentraBluetoothSDK.podspec +3 -1
  86. package/ios/MentraPhotoReceiverModule.swift +62 -10
  87. package/ios/Packages/CoreObjC/include/PcmConverter.h +22 -0
  88. package/ios/Packages/SherpaOnnx/SherpaOnnx.swift +668 -26
  89. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/Info.plist +13 -9
  90. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64/Headers/sherpa-onnx/c-api/c-api.h +4551 -0
  91. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64/Headers/sherpa-onnx/c-api/cxx-api.h +1745 -0
  92. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64/libsherpa-onnx.a +0 -0
  93. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64_x86_64-simulator/Headers/sherpa-onnx/c-api/c-api.h +4551 -0
  94. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64_x86_64-simulator/Headers/sherpa-onnx/c-api/cxx-api.h +1745 -0
  95. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64_x86_64-simulator/libsherpa-onnx.a +0 -0
  96. package/ios/Source/BluetoothSdkDefaults.swift +6 -0
  97. package/ios/Source/Bridge.swift +68 -19
  98. package/ios/Source/DeviceManager.swift +167 -32
  99. package/ios/Source/DeviceStore.swift +24 -8
  100. package/ios/Source/MentraBluetoothSDK.swift +803 -47
  101. package/ios/Source/ObservableStore.swift +6 -0
  102. package/ios/Source/{Audio → audio}/AudioModels.swift +9 -7
  103. package/ios/Source/{Camera → camera}/CameraModels.swift +226 -16
  104. package/ios/Source/controllers/ControllerManager.swift +4 -3
  105. package/ios/Source/controllers/R1.swift +3 -3
  106. package/ios/Source/{Events → events}/BluetoothEvents.swift +183 -1
  107. package/ios/Source/{Internal → internal}/ValueParsing.swift +10 -1
  108. package/ios/Source/{Requests → requests}/DisplayRequests.swift +28 -4
  109. package/ios/Source/services/PhoneMic.swift +1 -0
  110. package/ios/Source/sgcs/G1.swift +5 -5
  111. package/ios/Source/sgcs/G2.swift +1494 -870
  112. package/ios/Source/sgcs/Mach1.swift +4 -4
  113. package/ios/Source/sgcs/MentraLive.swift +814 -321
  114. package/ios/Source/sgcs/MentraNex.swift +604 -244
  115. package/ios/Source/sgcs/SGCManager.swift +68 -5
  116. package/ios/Source/sgcs/Simulated.swift +9 -5
  117. package/ios/Source/sgcs/mentraos_ble.pb.swift +3073 -3134
  118. package/ios/Source/status/DeviceStatus.swift +974 -0
  119. package/ios/Source/{Status → status}/WifiHotspotStatus.swift +4 -4
  120. package/ios/Source/{Streaming → streaming}/StreamModels.swift +24 -9
  121. package/ios/Source/stt/STTTools.swift +27 -12
  122. package/ios/Source/stt/SherpaOnnxTranscriber.swift +31 -8
  123. package/ios/Source/tts/TTSTools.swift +164 -0
  124. package/ios/Source/utils/JSCExperiment.swift +7 -7
  125. package/ios/Source/utils/MessageChunkReassembler.swift +20 -1
  126. package/ios/Source/utils/MessageChunker.swift +78 -21
  127. package/ios/Source/utils/TarBz2Extractor.swift +53 -2
  128. package/package.json +1 -1
  129. package/plugin/build/withAndroid.js +68 -4
  130. package/src/BluetoothSdk.types.ts +344 -60
  131. package/src/_private/BluetoothSdkModule.ts +132 -102
  132. package/src/_private/photoRequestPayload.ts +28 -0
  133. package/src/index.ts +61 -15
  134. package/src/react/useMentraBluetooth.ts +6 -4
  135. package/android/.project +0 -28
  136. package/android/lc3Lib/.project +0 -28
  137. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/Headers/cargs.h +0 -162
  138. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/Headers/sherpa-onnx/c-api/c-api.h +0 -1852
  139. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/Headers/sherpa-onnx/c-api/cxx-api.h +0 -674
  140. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64/sherpa-onnx.a +0 -0
  141. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64_x86_64-simulator/sherpa-onnx.a +0 -0
  142. package/ios/Source/Status/DeviceStatus.swift +0 -471
  143. /package/ios/Source/{Connection → connection}/ScanSession.swift +0 -0
  144. /package/ios/Source/{Errors → errors}/BluetoothError.swift +0 -0
  145. /package/ios/Source/{Internal → internal}/BluetoothAvailability.swift +0 -0
  146. /package/ios/Source/{Status → status}/RuntimeState.swift +0 -0
  147. /package/ios/Source/{Types → types}/DeviceModels.swift +0 -0
@@ -0,0 +1,98 @@
1
+ package com.mentra.bluetoothsdk.utils;
2
+
3
+ import static org.assertj.core.api.Assertions.assertThat;
4
+
5
+ import android.graphics.Bitmap;
6
+ import android.graphics.Color;
7
+
8
+ import androidx.exifinterface.media.ExifInterface;
9
+
10
+ import org.json.JSONObject;
11
+ import org.junit.Rule;
12
+ import org.junit.Test;
13
+ import org.junit.rules.TemporaryFolder;
14
+ import org.junit.runner.RunWith;
15
+ import org.robolectric.RobolectricTestRunner;
16
+ import org.robolectric.annotation.Config;
17
+
18
+ import java.io.File;
19
+ import java.io.FileOutputStream;
20
+
21
+ @RunWith(RobolectricTestRunner.class)
22
+ @Config(sdk = 31)
23
+ public class BlePhotoUploadServiceTest {
24
+
25
+ @Rule public TemporaryFolder tempFolder = new TemporaryFolder();
26
+
27
+ @Test
28
+ public void describeContainer_detectsJpegAndAvifBrands() {
29
+ byte[] jpegHeader = {(byte) 0xFF, (byte) 0xD8, 0x00};
30
+ assertThat(BlePhotoUploadService.describeContainer(jpegHeader)).isEqualTo("jpeg");
31
+
32
+ byte[] avif =
33
+ new byte[] {
34
+ 0, 0, 0, 0,
35
+ 'f', 't', 'y', 'p',
36
+ 'a', 'v', 'i', 'f',
37
+ };
38
+ assertThat(BlePhotoUploadService.describeContainer(avif)).isEqualTo("iso_bmff/ftyp=avif");
39
+ }
40
+
41
+ @Test
42
+ public void containsExifMarkerInBytes_findsExifHeader() {
43
+ byte[] withExif = new byte[] {0, 1, 'E', 'x', 'i', 'f', 0, 0, 2};
44
+ assertThat(BlePhotoUploadService.containsExifMarkerInBytes(withExif)).isTrue();
45
+ assertThat(BlePhotoUploadService.containsExifMarkerInBytes(new byte[] {0, 1, 2, 3})).isFalse();
46
+ }
47
+
48
+ @Test
49
+ public void readImuJsonFromBleImage_parsesTiffUserCommentInAvif() throws Exception {
50
+ byte[] avif =
51
+ java.nio.file.Files.readAllBytes(
52
+ new File("src/test/resources/avif_with_exif.avif").toPath());
53
+ File temp = tempFolder.newFile("ble.avif");
54
+ java.nio.file.Files.write(temp.toPath(), avif);
55
+ String json = BlePhotoUploadService.readImuJsonFromBleImage(avif, temp.getAbsolutePath());
56
+ assertThat(json).isNotNull();
57
+ assertThat(new JSONObject(json).getInt("sampleCount")).isEqualTo(5);
58
+ }
59
+
60
+ @Test
61
+ public void convertToJpegPreservesUserComment() throws Exception {
62
+ File input = tempFolder.newFile("input.jpg");
63
+ writeMinimalJpeg(input);
64
+ JSONObject payload = new JSONObject();
65
+ payload.put("version", 1);
66
+ payload.put("sampleCount", 2);
67
+ payload.put("samplingRateHz", 100);
68
+ payload.put("startTimeNs", 1000L);
69
+ payload.put("durationMs", 20);
70
+ payload.put("samples", new org.json.JSONArray());
71
+ ExifInterface inputExif = new ExifInterface(input.getAbsolutePath());
72
+ inputExif.setAttribute(ExifInterface.TAG_USER_COMMENT, payload.toString());
73
+ inputExif.saveAttributes();
74
+
75
+ byte[] inputBytes = java.nio.file.Files.readAllBytes(input.toPath());
76
+ byte[] outputBytes = BlePhotoUploadService.convertToJpegPreservingExif(inputBytes);
77
+
78
+ File output = tempFolder.newFile("out.jpg");
79
+ try (FileOutputStream fos = new FileOutputStream(output)) {
80
+ fos.write(outputBytes);
81
+ }
82
+
83
+ ExifInterface exif = new ExifInterface(output.getAbsolutePath());
84
+ String comment = exif.getAttribute(ExifInterface.TAG_USER_COMMENT);
85
+ assertThat(comment).isNotNull();
86
+ assertThat(new JSONObject(comment).getInt("sampleCount")).isEqualTo(2);
87
+ }
88
+
89
+ private static void writeMinimalJpeg(File dest) throws Exception {
90
+ Bitmap bitmap = Bitmap.createBitmap(2, 2, Bitmap.Config.ARGB_8888);
91
+ bitmap.eraseColor(Color.BLACK);
92
+ try (FileOutputStream fos = new FileOutputStream(dest)) {
93
+ bitmap.compress(Bitmap.CompressFormat.JPEG, 90, fos);
94
+ } finally {
95
+ bitmap.recycle();
96
+ }
97
+ }
98
+ }
@@ -3,7 +3,6 @@ export type GlassesNotReadyEvent = {
3
3
  message: string;
4
4
  };
5
5
  export type ButtonPressEvent = {
6
- type: "button_press";
7
6
  buttonId: string;
8
7
  pressType: "long" | "short";
9
8
  timestamp: number;
@@ -14,6 +13,13 @@ export type TouchEvent = {
14
13
  gestureName: string;
15
14
  timestamp: number;
16
15
  };
16
+ export type AccelEvent = {
17
+ type: "accel_event";
18
+ x: number;
19
+ y: number;
20
+ z: number;
21
+ timestamp: number;
22
+ };
17
23
  export type HeadUpEvent = {
18
24
  up: boolean;
19
25
  };
@@ -21,6 +27,7 @@ export type VoiceActivityDetectionStatusEvent = {
21
27
  type: "voice_activity_detection_status";
22
28
  voiceActivityDetectionEnabled: boolean;
23
29
  };
30
+ export declare const DEFAULT_VOICE_ACTIVITY_DETECTION_ENABLED = false;
24
31
  export type SpeakingStatusEvent = {
25
32
  type: "speaking_status";
26
33
  speaking: boolean;
@@ -33,19 +40,19 @@ export type BatteryStatusEvent = {
33
40
  timestamp: number;
34
41
  };
35
42
  export type GlassesConnectionStatus = {
36
- state: 'disconnected';
43
+ state: "disconnected";
37
44
  } | {
38
- state: 'scanning';
45
+ state: "scanning";
39
46
  } | {
40
- state: 'connecting';
47
+ state: "connecting";
41
48
  } | {
42
- state: 'bonding';
49
+ state: "bonding";
43
50
  } | {
44
- state: 'connected';
51
+ state: "connected";
45
52
  fullyBooted: boolean;
46
53
  };
47
54
  export type ConnectedGlassesConnectionStatus = Extract<GlassesConnectionStatus, {
48
- state: 'connected';
55
+ state: "connected";
49
56
  }>;
50
57
  export declare function isConnectedGlassesConnectionStatus(status: GlassesConnectionStatus): status is ConnectedGlassesConnectionStatus;
51
58
  export declare function isReadyGlassesConnectionStatus(status: GlassesConnectionStatus): boolean;
@@ -69,29 +76,29 @@ export type LogEvent = {
69
76
  message: string;
70
77
  };
71
78
  export type WifiStatus = {
72
- state: 'disconnected';
79
+ state: "disconnected";
73
80
  } | {
74
- state: 'connected';
81
+ state: "connected";
75
82
  ssid: string;
76
83
  localIp?: string;
77
84
  };
78
85
  export type ConnectedWifiStatus = Extract<WifiStatus, {
79
- state: 'connected';
86
+ state: "connected";
80
87
  }>;
81
88
  export declare function isConnectedWifiStatus(status: WifiStatus): status is ConnectedWifiStatus;
82
89
  export type WifiStatusChangeEvent = WifiStatus & {
83
90
  type: "wifi_status_change";
84
91
  };
85
92
  export type HotspotStatus = {
86
- state: 'disabled';
93
+ state: "disabled";
87
94
  } | {
88
- state: 'enabled';
95
+ state: "enabled";
89
96
  ssid: string;
90
97
  password: string;
91
98
  localIp: string;
92
99
  };
93
100
  export type EnabledHotspotStatus = Extract<HotspotStatus, {
94
- state: 'enabled';
101
+ state: "enabled";
95
102
  }>;
96
103
  export declare function isEnabledHotspotStatus(status: HotspotStatus): status is EnabledHotspotStatus;
97
104
  export type HotspotStatusChangeEvent = HotspotStatus & {
@@ -102,11 +109,33 @@ export type HotspotErrorEvent = {
102
109
  errorMessage: string;
103
110
  timestamp: number;
104
111
  };
112
+ export type VersionInfoResult = {
113
+ androidVersion: string;
114
+ firmwareVersion: string;
115
+ besFirmwareVersion: string;
116
+ mtkFirmwareVersion: string;
117
+ buildNumber: string;
118
+ systemTimeMs?: number;
119
+ otaVersionUrl: string;
120
+ appVersion: string;
121
+ };
122
+ export type VersionInfoEvent = VersionInfoResult & {
123
+ type: "version_info";
124
+ };
125
+ export type WifiScanResultEvent = {
126
+ type: "wifi_scan_result";
127
+ networks: WifiSearchResult[];
128
+ scanComplete?: boolean;
129
+ };
105
130
  export type PhotoResponseEvent = {
106
131
  type: "photo_response";
107
132
  state: "success";
108
133
  requestId: string;
109
134
  uploadUrl: string;
135
+ photoUrl?: string;
136
+ statusUrl?: string;
137
+ contentType?: string;
138
+ fileSizeBytes?: number;
110
139
  timestamp: number;
111
140
  } | {
112
141
  type: "photo_response";
@@ -116,7 +145,10 @@ export type PhotoResponseEvent = {
116
145
  errorCode?: string;
117
146
  errorMessage: string;
118
147
  };
119
- export type PhotoStatusState = "accepted" | "queued" | "configuring" | "capturing" | "captured" | "compressing" | "uploading" | "uploaded" | "ready_for_transfer" | "transferring" | "failed";
148
+ export type PhotoSuccessResponseEvent = Extract<PhotoResponseEvent, {
149
+ state: "success";
150
+ }>;
151
+ export type PhotoStatusState = "accepted" | "queued" | "configuring" | "capturing" | "captured" | "compressing" | "ble_fallback_compression" | "uploading" | "uploaded" | "ready_for_transfer" | "transferring" | "failed";
120
152
  export type PhotoResolvedConfig = {
121
153
  format?: "jpeg" | string;
122
154
  width?: number;
@@ -130,15 +162,80 @@ export type PhotoResolvedConfig = {
130
162
  exposureTimeNs?: number;
131
163
  iso?: number;
132
164
  };
165
+ export type PhotoFpsRange = {
166
+ min?: number;
167
+ max?: number;
168
+ };
169
+ export type PhotoRequestedCaptureConfig = {
170
+ manual?: boolean;
171
+ exposureTimeNs?: number;
172
+ iso?: number;
173
+ frameDurationNs?: number;
174
+ aeMode?: number;
175
+ aeLock?: boolean;
176
+ aeExposureCompensation?: number;
177
+ aeTargetFpsRange?: PhotoFpsRange;
178
+ noiseReductionMode?: number;
179
+ edgeMode?: number;
180
+ afMode?: number;
181
+ zsl?: boolean;
182
+ };
183
+ export type PhotoMeteredPreview = {
184
+ exposureTimeNs?: number;
185
+ iso?: number;
186
+ totalLightProxy?: number;
187
+ };
188
+ export type PhotoCaptureMetadata = {
189
+ manual?: boolean;
190
+ exposureTimeNs?: number;
191
+ iso?: number;
192
+ frameDurationNs?: number;
193
+ aeMode?: number;
194
+ aeState?: number;
195
+ aeStateName?: string;
196
+ noiseReductionMode?: number;
197
+ edgeMode?: number;
198
+ zsl?: boolean;
199
+ sensorTimestampNs?: number;
200
+ totalLightProxy?: number;
201
+ mfnrLikely?: boolean;
202
+ };
133
203
  export type PhotoStatusEvent = {
134
204
  type: "photo_status";
135
205
  requestId: string;
136
206
  status: PhotoStatusState | string;
137
207
  timestamp: number;
138
208
  resolvedConfig?: PhotoResolvedConfig;
209
+ requestedCaptureConfig?: PhotoRequestedCaptureConfig;
210
+ meteredPreview?: PhotoMeteredPreview;
211
+ captureMetadata?: PhotoCaptureMetadata;
139
212
  errorCode?: string;
140
213
  errorMessage?: string;
141
214
  };
215
+ export type VideoRecordingStatusEvent = {
216
+ type: "video_recording_status";
217
+ requestId?: string;
218
+ success: boolean;
219
+ status: VideoRecordingStatusState;
220
+ details?: string | null;
221
+ timestamp: number;
222
+ data?: {
223
+ recording?: boolean;
224
+ duration_ms?: number;
225
+ duration_formatted?: string;
226
+ [key: string]: unknown;
227
+ };
228
+ };
229
+ export type VideoRecordingStatusState = "recording_started" | "recording_status" | "already_recording" | "recording_stopped" | "not_recording" | "request_id_mismatch" | "service_unavailable" | "json_error" | "battery_low" | "camera_busy" | "storage_unavailable" | "integrity_failed" | "error";
230
+ export type VideoRecordingStartedStatusEvent = Omit<VideoRecordingStatusEvent, "success" | "status"> & {
231
+ success: true;
232
+ status: "recording_started";
233
+ };
234
+ export type VideoRecordingStoppedStatusEvent = Omit<VideoRecordingStatusEvent, "success" | "status"> & {
235
+ success: true;
236
+ status: "recording_stopped";
237
+ };
238
+ export type VideoRecordingSuccessStatusEvent = VideoRecordingStartedStatusEvent | VideoRecordingStoppedStatusEvent;
142
239
  export type GalleryStatusEvent = {
143
240
  type: "gallery_status";
144
241
  photos: number;
@@ -147,6 +244,7 @@ export type GalleryStatusEvent = {
147
244
  totalSize?: number;
148
245
  hasContent: boolean;
149
246
  cameraBusy: boolean;
247
+ cameraBusyReason?: "video" | "stream" | (string & {});
150
248
  };
151
249
  export type CompatibleGlassesSearchStopEvent = {
152
250
  type: "compatible_glasses_search_stop";
@@ -185,11 +283,49 @@ export type RgbLedControlResponseEvent = {
185
283
  requestId: string;
186
284
  errorCode: string;
187
285
  };
286
+ export type RgbLedControlSuccessResponseEvent = Extract<RgbLedControlResponseEvent, {
287
+ state: "success";
288
+ }>;
289
+ export type SettingsAckStatus = "applied" | "ready" | "error" | "failed" | "failure" | "rejected";
290
+ export type SettingsAckSetting = "gallery_mode" | "button_photo" | "button_video_recording" | "button_camera_led" | "button_max_recording_time" | "camera_fov";
291
+ export type SettingsAckEvent = {
292
+ type: "settings_ack";
293
+ requestId: string;
294
+ setting: SettingsAckSetting;
295
+ status: SettingsAckStatus;
296
+ timestamp: number;
297
+ fov?: number;
298
+ roiPosition?: CameraRoiPositionValue;
299
+ hardwareApplied?: boolean;
300
+ active?: boolean;
301
+ size?: ButtonPhotoSize | string;
302
+ width?: number;
303
+ height?: number;
304
+ fps?: number;
305
+ enabled?: boolean;
306
+ minutes?: number;
307
+ errorCode?: string;
308
+ errorMessage?: string;
309
+ };
310
+ export type SettingsAckSuccessStatus = Exclude<SettingsAckStatus, "error" | "failed" | "failure" | "rejected">;
311
+ export type SettingsAckSuccessEvent = Omit<SettingsAckEvent, "status"> & {
312
+ status: SettingsAckSuccessStatus;
313
+ };
188
314
  export type RgbLedAction = "on" | "off";
189
315
  export type RgbLedColor = "red" | "green" | "blue" | "orange" | "white";
190
316
  export type PhotoSize = "small" | "medium" | "large" | "full";
191
- export type ButtonPhotoSize = "small" | "medium" | "large";
317
+ export type ButtonPhotoSize = "small" | "medium" | "large" | "max";
192
318
  export type PhotoCompression = "none" | "medium" | "heavy";
319
+ /**
320
+ * Optional per-recording video settings for {@link startVideoRecording}. When
321
+ * omitted, the glasses fall back to their saved button-video settings. Any
322
+ * field left undefined is omitted from the BLE command (glasses default applies).
323
+ */
324
+ export interface VideoRecordingSettings {
325
+ width?: number;
326
+ height?: number;
327
+ fps?: number;
328
+ }
193
329
  export declare const DeviceModels: {
194
330
  readonly Simulated: "Simulated Glasses";
195
331
  readonly G1: "Even Realities G1";
@@ -208,21 +344,31 @@ export type DashboardMenuItem = {
208
344
  packageName: string;
209
345
  values?: Record<string, unknown>;
210
346
  };
211
- export declare const CAMERA_FOV_MIN = 82;
347
+ export declare const CAMERA_FOV_MIN = 62;
212
348
  export declare const CAMERA_FOV_MAX = 118;
213
349
  export declare const CAMERA_FOV_DEFAULT = 102;
214
- export type CameraRoiPosition = 0 | 1 | 2;
215
- export type CameraFov = {
350
+ export type CameraRoiPosition = "center" | "bottom" | "top";
351
+ export type CameraRoiPositionValue = 0 | 1 | 2;
352
+ export type CameraFovPreset = "narrow" | "standard" | "wide";
353
+ export type CameraFovRequest = {
216
354
  fov: number;
217
355
  roiPosition?: CameraRoiPosition;
356
+ } | {
357
+ preset: CameraFovPreset;
218
358
  };
219
- export type CameraFovSetting = {
359
+ export type CameraFovResult = {
360
+ requestId: string;
220
361
  fov: number;
221
362
  roiPosition: CameraRoiPosition;
363
+ timestamp: number;
364
+ };
365
+ export type CameraFovSetting = {
366
+ fov: number;
367
+ roiPosition: CameraRoiPositionValue;
222
368
  };
223
369
  type NativeCameraFovSetting = {
224
370
  fov: number;
225
- roi_position: CameraRoiPosition;
371
+ roi_position: CameraRoiPositionValue;
226
372
  };
227
373
  export type MicPreference = "auto" | "phone" | "glasses" | "bluetooth";
228
374
  export type MicMode = "phone" | "glasses" | "bluetoothClassic" | "bluetooth";
@@ -233,6 +379,7 @@ export type PhotoRequestParams = {
233
379
  webhookUrl: string | null;
234
380
  authToken: string | null;
235
381
  compress: PhotoCompression;
382
+ save?: boolean;
236
383
  sound: boolean;
237
384
  exposureTimeNs?: number | null;
238
385
  /** Sensor ISO for this capture only. Only used when exposureTimeNs enables manual exposure. */
@@ -317,18 +464,27 @@ export type MicLc3Event = {
317
464
  export type StreamStatusLifecycleState = "initializing" | "streaming" | "stopping" | "stopped";
318
465
  export type StreamStatusReconnectState = "reconnecting" | "reconnected" | "reconnect_failed";
319
466
  export type StreamStatusState = StreamStatusLifecycleState | StreamStatusReconnectState | "error";
467
+ /** Effective stream settings reported by the glasses after defaults and clamps. */
320
468
  export type StreamResolvedConfig = {
321
469
  transport?: "rtmp" | "srt" | "whip";
322
470
  video?: {
471
+ /** Encoded output width sent to the stream endpoint. */
323
472
  width: number;
473
+ /** Encoded output height sent to the stream endpoint. */
324
474
  height: number;
475
+ /** Native camera buffer width selected before crop/downscale. */
325
476
  captureWidth?: number;
477
+ /** Native camera buffer height selected before crop/downscale. */
326
478
  captureHeight?: number;
479
+ /** Encoded video bitrate in bits per second. */
327
480
  bitrate: number;
481
+ /** Resolved capture/encode frame rate. */
328
482
  fps: number;
329
483
  };
330
484
  audio?: {
485
+ /** Encoded audio bitrate in bits per second. */
331
486
  bitrate?: number;
487
+ /** Audio sample rate in Hz. */
332
488
  sampleRate?: number;
333
489
  echoCancellation?: boolean;
334
490
  noiseSuppression?: boolean;
@@ -407,13 +563,14 @@ export type OtaStatusEvent = {
407
563
  session_id: string;
408
564
  total_steps: number;
409
565
  current_step: number;
410
- step_type: 'apk' | 'mtk' | 'bes';
411
- phase: 'download' | 'install';
566
+ step_type: "apk" | "mtk" | "bes";
567
+ phase: "download" | "install";
412
568
  step_percent: number;
413
569
  overall_percent: number;
414
- status: 'in_progress' | 'step_complete' | 'complete' | 'failed' | 'idle';
570
+ status: "in_progress" | "step_complete" | "complete" | "failed" | "idle";
415
571
  error_message?: string;
416
572
  };
573
+ export type OtaQueryResult = OtaUpdateAvailableEvent | OtaStatusEvent;
417
574
  /** Nex BLE protobuf trace (NexEventUtils); payload matches native Map keys. */
418
575
  export type BleCommandTraceEvent = {
419
576
  command: string;
@@ -436,16 +593,19 @@ export type BluetoothSdkModuleEvents = {
436
593
  glasses_not_ready: (event: GlassesNotReadyEvent) => void;
437
594
  button_press: (event: ButtonPressEvent) => void;
438
595
  touch_event: (event: TouchEvent) => void;
596
+ accel_event: (event: AccelEvent) => void;
439
597
  head_up: (event: HeadUpEvent) => void;
440
598
  voice_activity_detection_status: (event: VoiceActivityDetectionStatusEvent) => void;
441
599
  speaking_status: (event: SpeakingStatusEvent) => void;
442
600
  battery_status: (event: BatteryStatusEvent) => void;
443
601
  local_transcription: (event: LocalTranscriptionEvent) => void;
444
602
  wifi_status_change: (event: WifiStatusChangeEvent) => void;
603
+ wifi_scan_result: (event: WifiScanResultEvent) => void;
445
604
  hotspot_status_change: (event: HotspotStatusChangeEvent) => void;
446
605
  hotspot_error: (event: HotspotErrorEvent) => void;
447
606
  photo_response: (event: PhotoResponseEvent) => void;
448
607
  photo_status: (event: PhotoStatusEvent) => void;
608
+ video_recording_status: (event: VideoRecordingStatusEvent) => void;
449
609
  gallery_status: (event: GalleryStatusEvent) => void;
450
610
  compatible_glasses_search_stop: (event: CompatibleGlassesSearchStopEvent) => void;
451
611
  heartbeat_sent: (event: HeartbeatSentEvent) => void;
@@ -453,6 +613,7 @@ export type BluetoothSdkModuleEvents = {
453
613
  swipe_volume_status: (event: SwipeVolumeStatusEvent) => void;
454
614
  switch_status: (event: SwitchStatusEvent) => void;
455
615
  rgb_led_control_response: (event: RgbLedControlResponseEvent) => void;
616
+ settings_ack: (event: SettingsAckEvent) => void;
456
617
  pair_failure: (event: PairFailureEvent) => void;
457
618
  audio_pairing_needed: (event: AudioPairingNeededEvent) => void;
458
619
  audio_connected: (event: AudioConnectedEvent) => void;
@@ -468,10 +629,17 @@ export type BluetoothSdkModuleEvents = {
468
629
  ota_update_available: (event: OtaUpdateAvailableEvent) => void;
469
630
  ota_start_ack: (event: OtaStartAckEvent) => void;
470
631
  ota_status: (event: OtaStatusEvent) => void;
632
+ version_info: (event: VersionInfoEvent) => void;
471
633
  send_command_to_ble: (event: BleCommandTraceEvent) => void;
472
634
  receive_command_from_ble: (event: BleCommandTraceEvent) => void;
473
635
  miniapp_selected: (event: MiniappSelectedEvent) => void;
636
+ extraction_progress: (event: ExtractionProgressEvent) => void;
474
637
  };
638
+ export interface ExtractionProgressEvent {
639
+ percentage: number;
640
+ bytesRead: number;
641
+ totalBytes: number;
642
+ }
475
643
  export type PublicGlassesStatus = Omit<GlassesStatus, "otaUpdateAvailable" | "otaProgress" | "otaInProgress" | "otaVersionUrl">;
476
644
  export type PublicBluetoothStatus = Pick<BluetoothStatus, "searching" | "searchingController" | "systemMicUnavailable" | "micRanking" | "currentMic" | "searchResults" | "wifiScanResults" | "lastLog" | "otherBtConnected" | "galleryModeEnabled">;
477
645
  export type BluetoothSdkEventMap = {
@@ -483,21 +651,25 @@ export type BluetoothSdkEventMap = {
483
651
  glasses_not_ready: GlassesNotReadyEvent;
484
652
  button_press: ButtonPressEvent;
485
653
  touch_event: TouchEvent;
654
+ accel_event: AccelEvent;
486
655
  head_up: HeadUpEvent;
487
656
  voice_activity_detection_status: VoiceActivityDetectionStatusEvent;
488
657
  speaking_status: SpeakingStatusEvent;
489
658
  battery_status: BatteryStatusEvent;
490
659
  local_transcription: LocalTranscriptionEvent;
491
660
  wifi_status_change: WifiStatusChangeEvent;
661
+ wifi_scan_result: WifiScanResultEvent;
492
662
  hotspot_status_change: HotspotStatusChangeEvent;
493
663
  hotspot_error: HotspotErrorEvent;
494
664
  photo_response: PhotoResponseEvent;
495
665
  photo_status: PhotoStatusEvent;
666
+ video_recording_status: VideoRecordingStatusEvent;
496
667
  gallery_status: GalleryStatusEvent;
497
668
  compatible_glasses_search_stop: CompatibleGlassesSearchStopEvent;
498
669
  swipe_volume_status: SwipeVolumeStatusEvent;
499
670
  switch_status: SwitchStatusEvent;
500
671
  rgb_led_control_response: RgbLedControlResponseEvent;
672
+ settings_ack: SettingsAckEvent;
501
673
  pair_failure: PairFailureEvent;
502
674
  audio_pairing_needed: AudioPairingNeededEvent;
503
675
  audio_connected: AudioConnectedEvent;
@@ -505,6 +677,11 @@ export type BluetoothSdkEventMap = {
505
677
  mic_pcm: MicPcmEvent;
506
678
  mic_lc3: MicLc3Event;
507
679
  stream_status: StreamStatusEvent;
680
+ ota_update_available: OtaUpdateAvailableEvent;
681
+ ota_start_ack: OtaStartAckEvent;
682
+ ota_status: OtaStatusEvent;
683
+ version_info: VersionInfoEvent;
684
+ extraction_progress: ExtractionProgressEvent;
508
685
  };
509
686
  export type BluetoothSdkEventName = keyof BluetoothSdkEventMap;
510
687
  export type BluetoothSdkEventListener<EventName extends BluetoothSdkEventName> = (event: BluetoothSdkEventMap[EventName]) => void;
@@ -531,31 +708,50 @@ export interface BluetoothSdkPublicModule {
531
708
  showDashboard(): Promise<void>;
532
709
  setDashboardPosition(height: number, depth: number): Promise<void>;
533
710
  setHeadUpAngle(angleDegrees: number): Promise<void>;
711
+ setImuEnabled(enabled: boolean): Promise<void>;
534
712
  setScreenDisabled(disabled: boolean): Promise<void>;
535
- requestWifiScan(): Promise<void>;
536
- sendWifiCredentials(ssid: string, password: string): Promise<void>;
537
- forgetWifiNetwork(ssid: string): Promise<void>;
538
- setHotspotState(enabled: boolean): Promise<void>;
539
- setGalleryModeEnabled(enabled: boolean): Promise<void>;
713
+ requestWifiScan(): Promise<WifiSearchResult[]>;
714
+ sendWifiCredentials(ssid: string, password: string): Promise<WifiStatusChangeEvent>;
715
+ forgetWifiNetwork(ssid: string): Promise<WifiStatusChangeEvent>;
716
+ setHotspotState(enabled: boolean): Promise<HotspotStatusChangeEvent>;
717
+ setGalleryModeEnabled(enabled: boolean): Promise<SettingsAckSuccessEvent>;
540
718
  setVoiceActivityDetectionEnabled(enabled: boolean): Promise<void>;
541
- setButtonPhotoSettings(size: ButtonPhotoSize): Promise<void>;
542
- setButtonVideoRecordingSettings(width: number, height: number, fps: number): Promise<void>;
543
- setButtonCameraLed(enabled: boolean): Promise<void>;
544
- setButtonMaxRecordingTime(minutes: number): Promise<void>;
545
- setCameraFov(fov: CameraFov): Promise<void>;
546
- queryGalleryStatus(): Promise<void>;
547
- requestPhoto(params: PhotoRequestParams): Promise<void>;
548
- startVideoRecording(requestId: string, save: boolean, sound: boolean): Promise<void>;
549
- stopVideoRecording(requestId: string): Promise<void>;
550
- startStream(params: StreamStartRequest): Promise<void>;
551
- stopStream(): Promise<void>;
719
+ setButtonPhotoSettings(size: ButtonPhotoSize): Promise<SettingsAckSuccessEvent>;
720
+ setButtonVideoRecordingSettings(width: number, height: number, fps: number): Promise<SettingsAckSuccessEvent>;
721
+ setButtonCameraLed(enabled: boolean): Promise<SettingsAckSuccessEvent>;
722
+ setButtonMaxRecordingTime(minutes: number): Promise<SettingsAckSuccessEvent>;
723
+ setCameraFov(request: CameraFovRequest): Promise<CameraFovResult>;
724
+ queryGalleryStatus(): Promise<GalleryStatusEvent>;
725
+ requestPhoto(params: PhotoRequestParams): Promise<PhotoSuccessResponseEvent>;
726
+ startVideoRecording(requestId: string, save: boolean, sound: boolean, settings?: VideoRecordingSettings): Promise<VideoRecordingStartedStatusEvent>;
727
+ stopVideoRecording(requestId: string): Promise<VideoRecordingStoppedStatusEvent>;
728
+ startStream(params: StreamStartRequest): Promise<StreamStatusEvent>;
729
+ stopStream(): Promise<StreamStatusEvent>;
552
730
  setMicState(enabled: boolean, useGlassesMic?: boolean, sendTranscript?: boolean, sendLc3Data?: boolean): Promise<void>;
553
731
  setPreferredMic(preferredMic: MicPreference): Promise<void>;
554
732
  setOwnAppAudioPlaying(playing: boolean): Promise<void>;
555
733
  getGlassesMediaVolume(): Promise<GlassesMediaVolumeGetResult>;
556
734
  setGlassesMediaVolume(level: number): Promise<GlassesMediaVolumeSetResult>;
557
- rgbLedControl(requestId: string, packageName: string | null, action: RgbLedAction, color: RgbLedColor | null, onDurationMs: number, offDurationMs: number, count: number): Promise<void>;
558
- requestVersionInfo(): Promise<void>;
735
+ rgbLedControl(requestId: string, packageName: string | null, action: RgbLedAction, color: RgbLedColor | null, onDurationMs: number, offDurationMs: number, count: number): Promise<RgbLedControlSuccessResponseEvent>;
736
+ requestVersionInfo(): Promise<VersionInfoResult>;
737
+ /** Ask connected Mentra Live glasses to check/report OTA availability and status. */
738
+ checkForOtaUpdate(): Promise<OtaQueryResult>;
739
+ /** Start the OTA flow after your app has presented the available update to the user. */
740
+ startOtaUpdate(): Promise<OtaStartAckEvent>;
741
+ /** Re-run the glasses-side OTA version check, mainly after correcting clock skew/TLS failures. */
742
+ retryOtaVersionCheck(): Promise<OtaQueryResult>;
743
+ setSttModelDetails(path: string, languageCode: string): Promise<void>;
744
+ getSttModelPath(): Promise<string>;
745
+ checkSttModelAvailable(): Promise<boolean>;
746
+ validateSttModel(path: string): Promise<boolean>;
747
+ extractTarBz2(sourcePath: string, destinationPath: string): Promise<boolean>;
748
+ restartTranscriber(): Promise<void>;
749
+ setTtsModelDetails(path: string, languageCode: string): Promise<void>;
750
+ getTtsModelPath(): Promise<string>;
751
+ getTtsModelLanguage(): Promise<string>;
752
+ checkTtsModelAvailable(): Promise<boolean>;
753
+ validateTtsModel(path: string): Promise<boolean>;
754
+ generateTtsAudio(text: string, modelPath: string, outputPath: string, speakerId: number, speed: number): Promise<boolean>;
559
755
  }
560
756
  export type OtaStage = "download" | "install";
561
757
  export type OtaProgressStatus = "STARTED" | "PROGRESS" | "FINISHED" | "FAILED";
@@ -563,11 +759,11 @@ export interface OtaStatus {
563
759
  sessionId: string;
564
760
  totalSteps: number;
565
761
  currentStep: number;
566
- stepType: 'apk' | 'mtk' | 'bes';
567
- phase: 'download' | 'install';
762
+ stepType: "apk" | "mtk" | "bes";
763
+ phase: "download" | "install";
568
764
  stepPercent: number;
569
765
  overallPercent: number;
570
- status: 'in_progress' | 'step_complete' | 'complete' | 'failed' | 'idle';
766
+ status: "in_progress" | "step_complete" | "complete" | "failed" | "idle";
571
767
  error?: string;
572
768
  }
573
769
  export interface OtaUpdateInfo {
@@ -604,6 +800,8 @@ export interface GlassesStatus {
604
800
  leftMacAddress: string;
605
801
  rightMacAddress: string;
606
802
  buildNumber: string;
803
+ /** Glasses System.currentTimeMillis() from last version_info (clock skew detection). */
804
+ systemTimeMs?: number;
607
805
  otaVersionUrl: string;
608
806
  appVersion: string;
609
807
  bluetoothName: string;
@@ -632,8 +830,15 @@ export interface CoreDashboardMenuItem {
632
830
  packageName: string;
633
831
  running: boolean;
634
832
  }
833
+ export interface CalendarEvent {
834
+ title: string;
835
+ location?: string;
836
+ time: string;
837
+ endDate: number;
838
+ }
635
839
  export interface CoreSettings {
636
840
  menu_apps: CoreDashboardMenuItem[];
841
+ calendar_events: CalendarEvent[];
637
842
  }
638
843
  export interface Device {
639
844
  /**
@@ -709,11 +914,15 @@ export type BluetoothSettingsUpdate = Partial<{
709
914
  screen_disabled: boolean;
710
915
  contextual_dashboard: boolean;
711
916
  head_up_angle: number;
917
+ imu_enabled: boolean;
712
918
  brightness: number;
713
919
  auto_brightness: boolean;
714
920
  dashboard_height: number;
715
921
  dashboard_depth: number;
716
922
  menu_apps: DashboardMenuItem[] | CoreDashboardMenuItem[] | Array<Record<string, unknown>> | null;
923
+ calendar_events: CalendarEvent[];
924
+ metric_system: boolean;
925
+ twelve_hour_time: boolean;
717
926
  gallery_mode: boolean;
718
927
  voice_activity_detection_enabled: boolean;
719
928
  button_photo_size: ButtonPhotoSize;