@mentra/bluetooth-sdk 0.1.8 → 0.1.10

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 +165 -27
  23. package/android/src/main/java/com/mentra/bluetoothsdk/Bridge.kt +89 -21
  24. package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +189 -16
  25. package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +22 -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 +123 -10
  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 +569 -220
  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 +176 -104
  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 +54 -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 +241 -43
  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 +36 -23
  68. package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
  69. package/build/_private/BluetoothSdkModule.js +30 -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 +23 -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 +174 -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 +58 -19
  98. package/ios/Source/DeviceManager.swift +149 -17
  99. package/ios/Source/DeviceStore.swift +16 -6
  100. package/ios/Source/MentraBluetoothSDK.swift +803 -47
  101. package/ios/Source/ObservableStore.swift +8 -2
  102. package/ios/Source/{Audio → audio}/AudioModels.swift +9 -7
  103. package/ios/Source/{Camera → camera}/CameraModels.swift +225 -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 +2 -2
  111. package/ios/Source/sgcs/G2.swift +645 -403
  112. package/ios/Source/sgcs/Mach1.swift +2 -2
  113. package/ios/Source/sgcs/MentraLive.swift +812 -319
  114. package/ios/Source/sgcs/MentraNex.swift +412 -182
  115. package/ios/Source/sgcs/SGCManager.swift +57 -3
  116. package/ios/Source/sgcs/Simulated.swift +7 -3
  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 +331 -59
  131. package/src/_private/BluetoothSdkModule.ts +113 -102
  132. package/src/_private/photoRequestPayload.ts +28 -0
  133. package/src/index.ts +58 -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;
@@ -21,6 +20,7 @@ export type VoiceActivityDetectionStatusEvent = {
21
20
  type: "voice_activity_detection_status";
22
21
  voiceActivityDetectionEnabled: boolean;
23
22
  };
23
+ export declare const DEFAULT_VOICE_ACTIVITY_DETECTION_ENABLED = false;
24
24
  export type SpeakingStatusEvent = {
25
25
  type: "speaking_status";
26
26
  speaking: boolean;
@@ -33,19 +33,19 @@ export type BatteryStatusEvent = {
33
33
  timestamp: number;
34
34
  };
35
35
  export type GlassesConnectionStatus = {
36
- state: 'disconnected';
36
+ state: "disconnected";
37
37
  } | {
38
- state: 'scanning';
38
+ state: "scanning";
39
39
  } | {
40
- state: 'connecting';
40
+ state: "connecting";
41
41
  } | {
42
- state: 'bonding';
42
+ state: "bonding";
43
43
  } | {
44
- state: 'connected';
44
+ state: "connected";
45
45
  fullyBooted: boolean;
46
46
  };
47
47
  export type ConnectedGlassesConnectionStatus = Extract<GlassesConnectionStatus, {
48
- state: 'connected';
48
+ state: "connected";
49
49
  }>;
50
50
  export declare function isConnectedGlassesConnectionStatus(status: GlassesConnectionStatus): status is ConnectedGlassesConnectionStatus;
51
51
  export declare function isReadyGlassesConnectionStatus(status: GlassesConnectionStatus): boolean;
@@ -69,29 +69,29 @@ export type LogEvent = {
69
69
  message: string;
70
70
  };
71
71
  export type WifiStatus = {
72
- state: 'disconnected';
72
+ state: "disconnected";
73
73
  } | {
74
- state: 'connected';
74
+ state: "connected";
75
75
  ssid: string;
76
76
  localIp?: string;
77
77
  };
78
78
  export type ConnectedWifiStatus = Extract<WifiStatus, {
79
- state: 'connected';
79
+ state: "connected";
80
80
  }>;
81
81
  export declare function isConnectedWifiStatus(status: WifiStatus): status is ConnectedWifiStatus;
82
82
  export type WifiStatusChangeEvent = WifiStatus & {
83
83
  type: "wifi_status_change";
84
84
  };
85
85
  export type HotspotStatus = {
86
- state: 'disabled';
86
+ state: "disabled";
87
87
  } | {
88
- state: 'enabled';
88
+ state: "enabled";
89
89
  ssid: string;
90
90
  password: string;
91
91
  localIp: string;
92
92
  };
93
93
  export type EnabledHotspotStatus = Extract<HotspotStatus, {
94
- state: 'enabled';
94
+ state: "enabled";
95
95
  }>;
96
96
  export declare function isEnabledHotspotStatus(status: HotspotStatus): status is EnabledHotspotStatus;
97
97
  export type HotspotStatusChangeEvent = HotspotStatus & {
@@ -102,11 +102,33 @@ export type HotspotErrorEvent = {
102
102
  errorMessage: string;
103
103
  timestamp: number;
104
104
  };
105
+ export type VersionInfoResult = {
106
+ androidVersion: string;
107
+ firmwareVersion: string;
108
+ besFirmwareVersion: string;
109
+ mtkFirmwareVersion: string;
110
+ buildNumber: string;
111
+ systemTimeMs?: number;
112
+ otaVersionUrl: string;
113
+ appVersion: string;
114
+ };
115
+ export type VersionInfoEvent = VersionInfoResult & {
116
+ type: "version_info";
117
+ };
118
+ export type WifiScanResultEvent = {
119
+ type: "wifi_scan_result";
120
+ networks: WifiSearchResult[];
121
+ scanComplete?: boolean;
122
+ };
105
123
  export type PhotoResponseEvent = {
106
124
  type: "photo_response";
107
125
  state: "success";
108
126
  requestId: string;
109
127
  uploadUrl: string;
128
+ photoUrl?: string;
129
+ statusUrl?: string;
130
+ contentType?: string;
131
+ fileSizeBytes?: number;
110
132
  timestamp: number;
111
133
  } | {
112
134
  type: "photo_response";
@@ -116,7 +138,10 @@ export type PhotoResponseEvent = {
116
138
  errorCode?: string;
117
139
  errorMessage: string;
118
140
  };
119
- export type PhotoStatusState = "accepted" | "queued" | "configuring" | "capturing" | "captured" | "compressing" | "uploading" | "uploaded" | "ready_for_transfer" | "transferring" | "failed";
141
+ export type PhotoSuccessResponseEvent = Extract<PhotoResponseEvent, {
142
+ state: "success";
143
+ }>;
144
+ export type PhotoStatusState = "accepted" | "queued" | "configuring" | "capturing" | "captured" | "compressing" | "ble_fallback_compression" | "uploading" | "uploaded" | "ready_for_transfer" | "transferring" | "failed";
120
145
  export type PhotoResolvedConfig = {
121
146
  format?: "jpeg" | string;
122
147
  width?: number;
@@ -130,15 +155,80 @@ export type PhotoResolvedConfig = {
130
155
  exposureTimeNs?: number;
131
156
  iso?: number;
132
157
  };
158
+ export type PhotoFpsRange = {
159
+ min?: number;
160
+ max?: number;
161
+ };
162
+ export type PhotoRequestedCaptureConfig = {
163
+ manual?: boolean;
164
+ exposureTimeNs?: number;
165
+ iso?: number;
166
+ frameDurationNs?: number;
167
+ aeMode?: number;
168
+ aeLock?: boolean;
169
+ aeExposureCompensation?: number;
170
+ aeTargetFpsRange?: PhotoFpsRange;
171
+ noiseReductionMode?: number;
172
+ edgeMode?: number;
173
+ afMode?: number;
174
+ zsl?: boolean;
175
+ };
176
+ export type PhotoMeteredPreview = {
177
+ exposureTimeNs?: number;
178
+ iso?: number;
179
+ totalLightProxy?: number;
180
+ };
181
+ export type PhotoCaptureMetadata = {
182
+ manual?: boolean;
183
+ exposureTimeNs?: number;
184
+ iso?: number;
185
+ frameDurationNs?: number;
186
+ aeMode?: number;
187
+ aeState?: number;
188
+ aeStateName?: string;
189
+ noiseReductionMode?: number;
190
+ edgeMode?: number;
191
+ zsl?: boolean;
192
+ sensorTimestampNs?: number;
193
+ totalLightProxy?: number;
194
+ mfnrLikely?: boolean;
195
+ };
133
196
  export type PhotoStatusEvent = {
134
197
  type: "photo_status";
135
198
  requestId: string;
136
199
  status: PhotoStatusState | string;
137
200
  timestamp: number;
138
201
  resolvedConfig?: PhotoResolvedConfig;
202
+ requestedCaptureConfig?: PhotoRequestedCaptureConfig;
203
+ meteredPreview?: PhotoMeteredPreview;
204
+ captureMetadata?: PhotoCaptureMetadata;
139
205
  errorCode?: string;
140
206
  errorMessage?: string;
141
207
  };
208
+ export type VideoRecordingStatusEvent = {
209
+ type: "video_recording_status";
210
+ requestId?: string;
211
+ success: boolean;
212
+ status: VideoRecordingStatusState;
213
+ details?: string | null;
214
+ timestamp: number;
215
+ data?: {
216
+ recording?: boolean;
217
+ duration_ms?: number;
218
+ duration_formatted?: string;
219
+ [key: string]: unknown;
220
+ };
221
+ };
222
+ 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";
223
+ export type VideoRecordingStartedStatusEvent = Omit<VideoRecordingStatusEvent, "success" | "status"> & {
224
+ success: true;
225
+ status: "recording_started";
226
+ };
227
+ export type VideoRecordingStoppedStatusEvent = Omit<VideoRecordingStatusEvent, "success" | "status"> & {
228
+ success: true;
229
+ status: "recording_stopped";
230
+ };
231
+ export type VideoRecordingSuccessStatusEvent = VideoRecordingStartedStatusEvent | VideoRecordingStoppedStatusEvent;
142
232
  export type GalleryStatusEvent = {
143
233
  type: "gallery_status";
144
234
  photos: number;
@@ -147,6 +237,7 @@ export type GalleryStatusEvent = {
147
237
  totalSize?: number;
148
238
  hasContent: boolean;
149
239
  cameraBusy: boolean;
240
+ cameraBusyReason?: "video" | "stream" | (string & {});
150
241
  };
151
242
  export type CompatibleGlassesSearchStopEvent = {
152
243
  type: "compatible_glasses_search_stop";
@@ -185,11 +276,49 @@ export type RgbLedControlResponseEvent = {
185
276
  requestId: string;
186
277
  errorCode: string;
187
278
  };
279
+ export type RgbLedControlSuccessResponseEvent = Extract<RgbLedControlResponseEvent, {
280
+ state: "success";
281
+ }>;
282
+ export type SettingsAckStatus = "applied" | "ready" | "error" | "failed" | "failure" | "rejected";
283
+ export type SettingsAckSetting = "gallery_mode" | "button_photo" | "button_video_recording" | "button_camera_led" | "button_max_recording_time" | "camera_fov";
284
+ export type SettingsAckEvent = {
285
+ type: "settings_ack";
286
+ requestId: string;
287
+ setting: SettingsAckSetting;
288
+ status: SettingsAckStatus;
289
+ timestamp: number;
290
+ fov?: number;
291
+ roiPosition?: CameraRoiPositionValue;
292
+ hardwareApplied?: boolean;
293
+ active?: boolean;
294
+ size?: ButtonPhotoSize | string;
295
+ width?: number;
296
+ height?: number;
297
+ fps?: number;
298
+ enabled?: boolean;
299
+ minutes?: number;
300
+ errorCode?: string;
301
+ errorMessage?: string;
302
+ };
303
+ export type SettingsAckSuccessStatus = Exclude<SettingsAckStatus, "error" | "failed" | "failure" | "rejected">;
304
+ export type SettingsAckSuccessEvent = Omit<SettingsAckEvent, "status"> & {
305
+ status: SettingsAckSuccessStatus;
306
+ };
188
307
  export type RgbLedAction = "on" | "off";
189
308
  export type RgbLedColor = "red" | "green" | "blue" | "orange" | "white";
190
309
  export type PhotoSize = "small" | "medium" | "large" | "full";
191
310
  export type ButtonPhotoSize = "small" | "medium" | "large";
192
311
  export type PhotoCompression = "none" | "medium" | "heavy";
312
+ /**
313
+ * Optional per-recording video settings for {@link startVideoRecording}. When
314
+ * omitted, the glasses fall back to their saved button-video settings. Any
315
+ * field left undefined is omitted from the BLE command (glasses default applies).
316
+ */
317
+ export interface VideoRecordingSettings {
318
+ width?: number;
319
+ height?: number;
320
+ fps?: number;
321
+ }
193
322
  export declare const DeviceModels: {
194
323
  readonly Simulated: "Simulated Glasses";
195
324
  readonly G1: "Even Realities G1";
@@ -208,21 +337,31 @@ export type DashboardMenuItem = {
208
337
  packageName: string;
209
338
  values?: Record<string, unknown>;
210
339
  };
211
- export declare const CAMERA_FOV_MIN = 82;
340
+ export declare const CAMERA_FOV_MIN = 62;
212
341
  export declare const CAMERA_FOV_MAX = 118;
213
342
  export declare const CAMERA_FOV_DEFAULT = 102;
214
- export type CameraRoiPosition = 0 | 1 | 2;
215
- export type CameraFov = {
343
+ export type CameraRoiPosition = "center" | "bottom" | "top";
344
+ export type CameraRoiPositionValue = 0 | 1 | 2;
345
+ export type CameraFovPreset = "narrow" | "standard" | "wide";
346
+ export type CameraFovRequest = {
216
347
  fov: number;
217
348
  roiPosition?: CameraRoiPosition;
349
+ } | {
350
+ preset: CameraFovPreset;
218
351
  };
219
- export type CameraFovSetting = {
352
+ export type CameraFovResult = {
353
+ requestId: string;
220
354
  fov: number;
221
355
  roiPosition: CameraRoiPosition;
356
+ timestamp: number;
357
+ };
358
+ export type CameraFovSetting = {
359
+ fov: number;
360
+ roiPosition: CameraRoiPositionValue;
222
361
  };
223
362
  type NativeCameraFovSetting = {
224
363
  fov: number;
225
- roi_position: CameraRoiPosition;
364
+ roi_position: CameraRoiPositionValue;
226
365
  };
227
366
  export type MicPreference = "auto" | "phone" | "glasses" | "bluetooth";
228
367
  export type MicMode = "phone" | "glasses" | "bluetoothClassic" | "bluetooth";
@@ -233,6 +372,7 @@ export type PhotoRequestParams = {
233
372
  webhookUrl: string | null;
234
373
  authToken: string | null;
235
374
  compress: PhotoCompression;
375
+ save?: boolean;
236
376
  sound: boolean;
237
377
  exposureTimeNs?: number | null;
238
378
  /** Sensor ISO for this capture only. Only used when exposureTimeNs enables manual exposure. */
@@ -317,18 +457,27 @@ export type MicLc3Event = {
317
457
  export type StreamStatusLifecycleState = "initializing" | "streaming" | "stopping" | "stopped";
318
458
  export type StreamStatusReconnectState = "reconnecting" | "reconnected" | "reconnect_failed";
319
459
  export type StreamStatusState = StreamStatusLifecycleState | StreamStatusReconnectState | "error";
460
+ /** Effective stream settings reported by the glasses after defaults and clamps. */
320
461
  export type StreamResolvedConfig = {
321
462
  transport?: "rtmp" | "srt" | "whip";
322
463
  video?: {
464
+ /** Encoded output width sent to the stream endpoint. */
323
465
  width: number;
466
+ /** Encoded output height sent to the stream endpoint. */
324
467
  height: number;
468
+ /** Native camera buffer width selected before crop/downscale. */
325
469
  captureWidth?: number;
470
+ /** Native camera buffer height selected before crop/downscale. */
326
471
  captureHeight?: number;
472
+ /** Encoded video bitrate in bits per second. */
327
473
  bitrate: number;
474
+ /** Resolved capture/encode frame rate. */
328
475
  fps: number;
329
476
  };
330
477
  audio?: {
478
+ /** Encoded audio bitrate in bits per second. */
331
479
  bitrate?: number;
480
+ /** Audio sample rate in Hz. */
332
481
  sampleRate?: number;
333
482
  echoCancellation?: boolean;
334
483
  noiseSuppression?: boolean;
@@ -407,13 +556,14 @@ export type OtaStatusEvent = {
407
556
  session_id: string;
408
557
  total_steps: number;
409
558
  current_step: number;
410
- step_type: 'apk' | 'mtk' | 'bes';
411
- phase: 'download' | 'install';
559
+ step_type: "apk" | "mtk" | "bes";
560
+ phase: "download" | "install";
412
561
  step_percent: number;
413
562
  overall_percent: number;
414
- status: 'in_progress' | 'step_complete' | 'complete' | 'failed' | 'idle';
563
+ status: "in_progress" | "step_complete" | "complete" | "failed" | "idle";
415
564
  error_message?: string;
416
565
  };
566
+ export type OtaQueryResult = OtaUpdateAvailableEvent | OtaStatusEvent;
417
567
  /** Nex BLE protobuf trace (NexEventUtils); payload matches native Map keys. */
418
568
  export type BleCommandTraceEvent = {
419
569
  command: string;
@@ -442,10 +592,12 @@ export type BluetoothSdkModuleEvents = {
442
592
  battery_status: (event: BatteryStatusEvent) => void;
443
593
  local_transcription: (event: LocalTranscriptionEvent) => void;
444
594
  wifi_status_change: (event: WifiStatusChangeEvent) => void;
595
+ wifi_scan_result: (event: WifiScanResultEvent) => void;
445
596
  hotspot_status_change: (event: HotspotStatusChangeEvent) => void;
446
597
  hotspot_error: (event: HotspotErrorEvent) => void;
447
598
  photo_response: (event: PhotoResponseEvent) => void;
448
599
  photo_status: (event: PhotoStatusEvent) => void;
600
+ video_recording_status: (event: VideoRecordingStatusEvent) => void;
449
601
  gallery_status: (event: GalleryStatusEvent) => void;
450
602
  compatible_glasses_search_stop: (event: CompatibleGlassesSearchStopEvent) => void;
451
603
  heartbeat_sent: (event: HeartbeatSentEvent) => void;
@@ -453,6 +605,7 @@ export type BluetoothSdkModuleEvents = {
453
605
  swipe_volume_status: (event: SwipeVolumeStatusEvent) => void;
454
606
  switch_status: (event: SwitchStatusEvent) => void;
455
607
  rgb_led_control_response: (event: RgbLedControlResponseEvent) => void;
608
+ settings_ack: (event: SettingsAckEvent) => void;
456
609
  pair_failure: (event: PairFailureEvent) => void;
457
610
  audio_pairing_needed: (event: AudioPairingNeededEvent) => void;
458
611
  audio_connected: (event: AudioConnectedEvent) => void;
@@ -468,10 +621,17 @@ export type BluetoothSdkModuleEvents = {
468
621
  ota_update_available: (event: OtaUpdateAvailableEvent) => void;
469
622
  ota_start_ack: (event: OtaStartAckEvent) => void;
470
623
  ota_status: (event: OtaStatusEvent) => void;
624
+ version_info: (event: VersionInfoEvent) => void;
471
625
  send_command_to_ble: (event: BleCommandTraceEvent) => void;
472
626
  receive_command_from_ble: (event: BleCommandTraceEvent) => void;
473
627
  miniapp_selected: (event: MiniappSelectedEvent) => void;
628
+ extraction_progress: (event: ExtractionProgressEvent) => void;
474
629
  };
630
+ export interface ExtractionProgressEvent {
631
+ percentage: number;
632
+ bytesRead: number;
633
+ totalBytes: number;
634
+ }
475
635
  export type PublicGlassesStatus = Omit<GlassesStatus, "otaUpdateAvailable" | "otaProgress" | "otaInProgress" | "otaVersionUrl">;
476
636
  export type PublicBluetoothStatus = Pick<BluetoothStatus, "searching" | "searchingController" | "systemMicUnavailable" | "micRanking" | "currentMic" | "searchResults" | "wifiScanResults" | "lastLog" | "otherBtConnected" | "galleryModeEnabled">;
477
637
  export type BluetoothSdkEventMap = {
@@ -489,15 +649,18 @@ export type BluetoothSdkEventMap = {
489
649
  battery_status: BatteryStatusEvent;
490
650
  local_transcription: LocalTranscriptionEvent;
491
651
  wifi_status_change: WifiStatusChangeEvent;
652
+ wifi_scan_result: WifiScanResultEvent;
492
653
  hotspot_status_change: HotspotStatusChangeEvent;
493
654
  hotspot_error: HotspotErrorEvent;
494
655
  photo_response: PhotoResponseEvent;
495
656
  photo_status: PhotoStatusEvent;
657
+ video_recording_status: VideoRecordingStatusEvent;
496
658
  gallery_status: GalleryStatusEvent;
497
659
  compatible_glasses_search_stop: CompatibleGlassesSearchStopEvent;
498
660
  swipe_volume_status: SwipeVolumeStatusEvent;
499
661
  switch_status: SwitchStatusEvent;
500
662
  rgb_led_control_response: RgbLedControlResponseEvent;
663
+ settings_ack: SettingsAckEvent;
501
664
  pair_failure: PairFailureEvent;
502
665
  audio_pairing_needed: AudioPairingNeededEvent;
503
666
  audio_connected: AudioConnectedEvent;
@@ -505,6 +668,11 @@ export type BluetoothSdkEventMap = {
505
668
  mic_pcm: MicPcmEvent;
506
669
  mic_lc3: MicLc3Event;
507
670
  stream_status: StreamStatusEvent;
671
+ ota_update_available: OtaUpdateAvailableEvent;
672
+ ota_start_ack: OtaStartAckEvent;
673
+ ota_status: OtaStatusEvent;
674
+ version_info: VersionInfoEvent;
675
+ extraction_progress: ExtractionProgressEvent;
508
676
  };
509
677
  export type BluetoothSdkEventName = keyof BluetoothSdkEventMap;
510
678
  export type BluetoothSdkEventListener<EventName extends BluetoothSdkEventName> = (event: BluetoothSdkEventMap[EventName]) => void;
@@ -532,30 +700,48 @@ export interface BluetoothSdkPublicModule {
532
700
  setDashboardPosition(height: number, depth: number): Promise<void>;
533
701
  setHeadUpAngle(angleDegrees: number): Promise<void>;
534
702
  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>;
703
+ requestWifiScan(): Promise<WifiSearchResult[]>;
704
+ sendWifiCredentials(ssid: string, password: string): Promise<WifiStatusChangeEvent>;
705
+ forgetWifiNetwork(ssid: string): Promise<WifiStatusChangeEvent>;
706
+ setHotspotState(enabled: boolean): Promise<HotspotStatusChangeEvent>;
707
+ setGalleryModeEnabled(enabled: boolean): Promise<SettingsAckSuccessEvent>;
540
708
  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>;
709
+ setButtonPhotoSettings(size: ButtonPhotoSize): Promise<SettingsAckSuccessEvent>;
710
+ setButtonVideoRecordingSettings(width: number, height: number, fps: number): Promise<SettingsAckSuccessEvent>;
711
+ setButtonCameraLed(enabled: boolean): Promise<SettingsAckSuccessEvent>;
712
+ setButtonMaxRecordingTime(minutes: number): Promise<SettingsAckSuccessEvent>;
713
+ setCameraFov(request: CameraFovRequest): Promise<CameraFovResult>;
714
+ queryGalleryStatus(): Promise<GalleryStatusEvent>;
715
+ requestPhoto(params: PhotoRequestParams): Promise<PhotoSuccessResponseEvent>;
716
+ startVideoRecording(requestId: string, save: boolean, sound: boolean, settings?: VideoRecordingSettings): Promise<VideoRecordingStartedStatusEvent>;
717
+ stopVideoRecording(requestId: string): Promise<VideoRecordingStoppedStatusEvent>;
718
+ startStream(params: StreamStartRequest): Promise<StreamStatusEvent>;
719
+ stopStream(): Promise<StreamStatusEvent>;
552
720
  setMicState(enabled: boolean, useGlassesMic?: boolean, sendTranscript?: boolean, sendLc3Data?: boolean): Promise<void>;
553
721
  setPreferredMic(preferredMic: MicPreference): Promise<void>;
554
722
  setOwnAppAudioPlaying(playing: boolean): Promise<void>;
555
723
  getGlassesMediaVolume(): Promise<GlassesMediaVolumeGetResult>;
556
724
  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>;
725
+ rgbLedControl(requestId: string, packageName: string | null, action: RgbLedAction, color: RgbLedColor | null, onDurationMs: number, offDurationMs: number, count: number): Promise<RgbLedControlSuccessResponseEvent>;
726
+ requestVersionInfo(): Promise<VersionInfoResult>;
727
+ /** Ask connected Mentra Live glasses to check/report OTA availability and status. */
728
+ checkForOtaUpdate(): Promise<OtaQueryResult>;
729
+ /** Start the OTA flow after your app has presented the available update to the user. */
730
+ startOtaUpdate(): Promise<OtaStartAckEvent>;
731
+ /** Re-run the glasses-side OTA version check, mainly after correcting clock skew/TLS failures. */
732
+ retryOtaVersionCheck(): Promise<OtaQueryResult>;
733
+ setSttModelDetails(path: string, languageCode: string): Promise<void>;
734
+ getSttModelPath(): Promise<string>;
735
+ checkSttModelAvailable(): Promise<boolean>;
736
+ validateSttModel(path: string): Promise<boolean>;
737
+ extractTarBz2(sourcePath: string, destinationPath: string): Promise<boolean>;
738
+ restartTranscriber(): Promise<void>;
739
+ setTtsModelDetails(path: string, languageCode: string): Promise<void>;
740
+ getTtsModelPath(): Promise<string>;
741
+ getTtsModelLanguage(): Promise<string>;
742
+ checkTtsModelAvailable(): Promise<boolean>;
743
+ validateTtsModel(path: string): Promise<boolean>;
744
+ generateTtsAudio(text: string, modelPath: string, outputPath: string, speakerId: number, speed: number): Promise<boolean>;
559
745
  }
560
746
  export type OtaStage = "download" | "install";
561
747
  export type OtaProgressStatus = "STARTED" | "PROGRESS" | "FINISHED" | "FAILED";
@@ -563,11 +749,11 @@ export interface OtaStatus {
563
749
  sessionId: string;
564
750
  totalSteps: number;
565
751
  currentStep: number;
566
- stepType: 'apk' | 'mtk' | 'bes';
567
- phase: 'download' | 'install';
752
+ stepType: "apk" | "mtk" | "bes";
753
+ phase: "download" | "install";
568
754
  stepPercent: number;
569
755
  overallPercent: number;
570
- status: 'in_progress' | 'step_complete' | 'complete' | 'failed' | 'idle';
756
+ status: "in_progress" | "step_complete" | "complete" | "failed" | "idle";
571
757
  error?: string;
572
758
  }
573
759
  export interface OtaUpdateInfo {
@@ -604,6 +790,8 @@ export interface GlassesStatus {
604
790
  leftMacAddress: string;
605
791
  rightMacAddress: string;
606
792
  buildNumber: string;
793
+ /** Glasses System.currentTimeMillis() from last version_info (clock skew detection). */
794
+ systemTimeMs?: number;
607
795
  otaVersionUrl: string;
608
796
  appVersion: string;
609
797
  bluetoothName: string;
@@ -632,8 +820,15 @@ export interface CoreDashboardMenuItem {
632
820
  packageName: string;
633
821
  running: boolean;
634
822
  }
823
+ export interface CalendarEvent {
824
+ title: string;
825
+ location?: string;
826
+ time: string;
827
+ endDate: number;
828
+ }
635
829
  export interface CoreSettings {
636
830
  menu_apps: CoreDashboardMenuItem[];
831
+ calendar_events: CalendarEvent[];
637
832
  }
638
833
  export interface Device {
639
834
  /**
@@ -714,6 +909,9 @@ export type BluetoothSettingsUpdate = Partial<{
714
909
  dashboard_height: number;
715
910
  dashboard_depth: number;
716
911
  menu_apps: DashboardMenuItem[] | CoreDashboardMenuItem[] | Array<Record<string, unknown>> | null;
912
+ calendar_events: CalendarEvent[];
913
+ metric_system: boolean;
914
+ twelve_hour_time: boolean;
717
915
  gallery_mode: boolean;
718
916
  voice_activity_detection_enabled: boolean;
719
917
  button_photo_size: ButtonPhotoSize;