@mentra/bluetooth-sdk 0.1.21-beta.0 → 0.1.21-beta.1

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 (89) hide show
  1. package/README.md +22 -11
  2. package/android/build.gradle +3 -0
  3. package/android/src/main/AndroidManifest.xml +3 -1
  4. package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkAnalytics.kt +34 -0
  5. package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkDefaults.kt +1 -0
  6. package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +43 -3
  7. package/android/src/main/java/com/mentra/bluetoothsdk/Bridge.kt +7 -2
  8. package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +82 -6
  9. package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +5 -1
  10. package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +32 -3
  11. package/android/src/main/java/com/mentra/bluetoothsdk/OtaManifest.kt +43 -3
  12. package/android/src/main/java/com/mentra/bluetoothsdk/camera/CameraModels.kt +17 -4
  13. package/android/src/main/java/com/mentra/bluetoothsdk/debug/BleTraceLogger.kt +1 -1
  14. package/android/src/main/java/com/mentra/bluetoothsdk/services/Foreground.kt +53 -0
  15. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Ar99.java +2653 -0
  16. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.kt +161 -14
  17. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/SGCManager.kt +3 -0
  18. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/ar99/ota/Ar99OtaManager.java +566 -0
  19. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/ar99/ota/OtaByteUtils.java +18 -0
  20. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/ar99/ota/OtaCommandConstants.java +35 -0
  21. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/ar99/ota/OtaCrc32Util.java +13 -0
  22. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/ar99/ota/OtaGattTransport.java +11 -0
  23. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/ar99/ota/OtaProtocol.java +92 -0
  24. package/android/src/main/java/com/mentra/bluetoothsdk/streaming/StreamModels.kt +38 -1
  25. package/android/src/main/java/com/mentra/bluetoothsdk/types/DeviceModels.kt +7 -0
  26. package/android/src/main/java/com/mentra/bluetoothsdk/utils/BleJsonCompact.java +8 -0
  27. package/android/src/main/java/com/mentra/bluetoothsdk/utils/Constants.kt +2 -1
  28. package/android/src/main/java/com/mentra/bluetoothsdk/utils/PhoneAudioMonitor.kt +45 -0
  29. package/android/src/main/java/com/mentra/bluetoothsdk/utils/audio/Ar99OpusPcmDecoder.java +398 -0
  30. package/android/src/test/java/com/mentra/bluetoothsdk/OtaManifestDowngradeTest.kt +86 -0
  31. package/android/src/test/java/com/mentra/bluetoothsdk/StreamStatusEventTest.kt +29 -0
  32. package/android/src/test/java/com/mentra/bluetoothsdk/camera/PhotoRequestTest.kt +30 -0
  33. package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/Ar99AdvertisementMatcherTest.java +22 -0
  34. package/android/src/test/java/com/mentra/bluetoothsdk/utils/BleJsonCompactTest.java +20 -0
  35. package/android/src/test/java/com/mentra/bluetoothsdk/utils/PhoneAudioMonitorTest.kt +45 -0
  36. package/build/BluetoothSdk.types.d.ts +58 -25
  37. package/build/BluetoothSdk.types.d.ts.map +1 -1
  38. package/build/BluetoothSdk.types.js +2 -1
  39. package/build/BluetoothSdk.types.js.map +1 -1
  40. package/build/_internal.d.ts +1 -0
  41. package/build/_internal.d.ts.map +1 -1
  42. package/build/_internal.js +1 -0
  43. package/build/_internal.js.map +1 -1
  44. package/build/_private/BluetoothSdkModule.d.ts +8 -2
  45. package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
  46. package/build/_private/BluetoothSdkModule.js +6 -3
  47. package/build/_private/BluetoothSdkModule.js.map +1 -1
  48. package/build/_private/cameraRequestPayload.d.ts +1 -1
  49. package/build/_private/cameraRequestPayload.d.ts.map +1 -1
  50. package/build/_private/cameraRequestPayload.js +6 -0
  51. package/build/_private/cameraRequestPayload.js.map +1 -1
  52. package/build/_private/photoRequestPayload.d.ts.map +1 -1
  53. package/build/_private/photoRequestPayload.js +10 -1
  54. package/build/_private/photoRequestPayload.js.map +1 -1
  55. package/build/_private/sdkOtaManifest.d.ts +20 -0
  56. package/build/_private/sdkOtaManifest.d.ts.map +1 -0
  57. package/build/_private/sdkOtaManifest.js +28 -0
  58. package/build/_private/sdkOtaManifest.js.map +1 -0
  59. package/build/index.d.ts +1 -1
  60. package/build/index.d.ts.map +1 -1
  61. package/build/index.js +7 -1
  62. package/build/index.js.map +1 -1
  63. package/ios/BluetoothSdkModule.swift +41 -2
  64. package/ios/Source/BluetoothSdkDefaults.swift +2 -1
  65. package/ios/Source/Bridge.swift +15 -3
  66. package/ios/Source/DeviceManager.swift +67 -17
  67. package/ios/Source/DeviceStore.swift +5 -1
  68. package/ios/Source/MentraBluetoothSDK.swift +37 -8
  69. package/ios/Source/OtaManifest.swift +47 -4
  70. package/ios/Source/camera/CameraModels.swift +20 -5
  71. package/ios/Source/internal/BleTraceLogger.swift +1 -1
  72. package/ios/Source/internal/BluetoothSdkAnalytics.swift +39 -0
  73. package/ios/Source/sgcs/Ar99.swift +2451 -0
  74. package/ios/Source/sgcs/MentraLive.swift +174 -38
  75. package/ios/Source/sgcs/SGCManager.swift +8 -0
  76. package/ios/Source/sgcs/ar99/ota/Ar99OtaManager.swift +637 -0
  77. package/ios/Source/streaming/StreamModels.swift +62 -5
  78. package/ios/Source/types/DeviceModels.swift +13 -0
  79. package/ios/Source/utils/BleJsonCompact.swift +8 -0
  80. package/ios/Source/utils/Constants.swift +2 -0
  81. package/ios/Source/utils/PhoneAudioMonitor.swift +4 -0
  82. package/package.json +1 -1
  83. package/src/BluetoothSdk.types.ts +61 -28
  84. package/src/_internal.ts +1 -0
  85. package/src/_private/BluetoothSdkModule.ts +20 -6
  86. package/src/_private/cameraRequestPayload.ts +8 -2
  87. package/src/_private/photoRequestPayload.ts +12 -2
  88. package/src/_private/sdkOtaManifest.ts +31 -0
  89. package/src/index.ts +9 -1
@@ -0,0 +1,86 @@
1
+ package com.mentra.bluetoothsdk
2
+
3
+ import org.json.JSONObject
4
+ import org.junit.Assert.assertEquals
5
+ import org.junit.Assert.assertFalse
6
+ import org.junit.Assert.assertTrue
7
+ import org.junit.Assert.fail
8
+ import org.junit.Test
9
+ import org.junit.runner.RunWith
10
+ import org.robolectric.RobolectricTestRunner
11
+ import org.robolectric.annotation.Config
12
+
13
+ @RunWith(RobolectricTestRunner::class)
14
+ @Config(sdk = [33])
15
+ class OtaManifestDowngradeTest {
16
+
17
+ private fun manifest(versionCode: Long): JSONObject =
18
+ JSONObject()
19
+ .put("apps", JSONObject().put("com.mentra.asg_client", JSONObject().put("versionCode", versionCode)))
20
+
21
+ private fun hasUpdate(
22
+ currentBuildNumber: String,
23
+ manifest: JSONObject,
24
+ floor: Long = 0L,
25
+ ): Boolean =
26
+ OtaManifestChecker.hasUpdate(
27
+ currentBuildNumber = currentBuildNumber,
28
+ currentMtkVersion = "",
29
+ currentBesVersion = "",
30
+ manifest = manifest,
31
+ downgradeFloorVersionCode = floor,
32
+ )
33
+
34
+ @Test
35
+ fun upgradeIsAlwaysAnUpdate() {
36
+ assertTrue(hasUpdate("49000000", manifest(49076573L)))
37
+ }
38
+
39
+ @Test
40
+ fun exactPinIsNotAnUpdateEvenWithAnEnabledFloor() {
41
+ assertFalse(hasUpdate("49000000", manifest(49000000L), floor = 49000000L))
42
+ }
43
+
44
+ @Test
45
+ fun downgradeNeedsAnEnabledFloor() {
46
+ // Default floor 0 (fail closed) disables downgrades for native/OEM callers.
47
+ assertFalse(hasUpdate("49076573", manifest(49000000L)))
48
+ // A positive floor at/below the pin enables it.
49
+ assertTrue(hasUpdate("49076573", manifest(49000000L), floor = 49000000L))
50
+ // A pin below the floor is refused.
51
+ assertFalse(hasUpdate("49076573", manifest(48999999L), floor = 49000000L))
52
+ }
53
+
54
+ @Test
55
+ fun exactPinIsNotAnUpdate() {
56
+ assertFalse(hasUpdate("49076573", manifest(49076573L)))
57
+ }
58
+
59
+ private fun legacyTopLevelManifest(versionCode: Long): JSONObject =
60
+ JSONObject().put("versionCode", versionCode)
61
+
62
+ @Test
63
+ fun legacyTopLevelManifestStaysUpgradeOnly() {
64
+ // No apps entry -> not an exact pin -> upgrade-only, never a false downgrade.
65
+ assertFalse(hasUpdate("49076573", legacyTopLevelManifest(49000000L)))
66
+ assertTrue(hasUpdate("49000000", legacyTopLevelManifest(49076573L)))
67
+ }
68
+
69
+ @Test
70
+ fun zeroedPinOnModernGlassesIsAnError() {
71
+ // An unverifiable pin must never present as "no update" on modern glasses.
72
+ try {
73
+ hasUpdate("49076573", manifest(0L))
74
+ fail("expected invalid_ota_manifest")
75
+ } catch (e: BluetoothSdkException) {
76
+ assertEquals("invalid_ota_manifest", e.code)
77
+ }
78
+ }
79
+
80
+ @Test
81
+ fun zeroedRescuePinIsInertForLegacyGlasses() {
82
+ // Pre-39 glasses are legitimately checked against the frozen rescue manifests, whose
83
+ // zeroed ASG pin must read as no APK update (the MTK patch chain is what matters there).
84
+ assertFalse(hasUpdate("38", manifest(0L)))
85
+ }
86
+ }
@@ -0,0 +1,29 @@
1
+ package com.mentra.bluetoothsdk
2
+
3
+ import org.assertj.core.api.Assertions.assertThat
4
+ import org.junit.Test
5
+
6
+ class StreamStatusEventTest {
7
+ @Test
8
+ fun `stream telemetry survives native status normalization`() {
9
+ val event =
10
+ StreamStatusEvent(
11
+ mapOf(
12
+ "status" to "streaming",
13
+ "streamId" to "stream-1",
14
+ "stats" to
15
+ mapOf(
16
+ "bitrate" to 912_345L,
17
+ "fps" to 19.8,
18
+ "droppedFrames" to 2L,
19
+ "duration" to 31L,
20
+ "temperatureC" to 54.6,
21
+ ),
22
+ )
23
+ )
24
+
25
+ assertThat(event.stats?.bitrate).isEqualTo(912_345L)
26
+ assertThat(event.stats?.temperatureC).isEqualTo(54.6)
27
+ assertThat((event.values["stats"] as Map<*, *>)["duration"]).isEqualTo(31L)
28
+ }
29
+ }
@@ -5,6 +5,7 @@ import com.mentra.bluetoothsdk.PhotoMode
5
5
  import com.mentra.bluetoothsdk.PhotoRequest
6
6
  import com.mentra.bluetoothsdk.PhotoSize
7
7
  import org.assertj.core.api.Assertions.assertThat
8
+ import org.assertj.core.api.Assertions.assertThatThrownBy
8
9
  import org.junit.Test
9
10
 
10
11
  class PhotoRequestTest {
@@ -108,4 +109,33 @@ class PhotoRequestTest {
108
109
 
109
110
  assertThat(request.transferMethod).isEqualTo("ble")
110
111
  }
112
+
113
+ @Test
114
+ fun `fromMap preserves direct transfer without BLE fallback`() {
115
+ val request =
116
+ PhotoRequest.fromMap(
117
+ mapOf(
118
+ "size" to "medium",
119
+ "transferMethod" to "direct",
120
+ "webhookUrl" to "https://example.com/upload",
121
+ )
122
+ )
123
+
124
+ assertThat(request.transferMethod).isEqualTo("direct")
125
+ }
126
+
127
+ @Test
128
+ fun `fromMap rejects an unknown transfer method`() {
129
+ assertThatThrownBy {
130
+ PhotoRequest.fromMap(
131
+ mapOf(
132
+ "size" to "medium",
133
+ "transferMethod" to "wifi",
134
+ "webhookUrl" to "https://example.com/upload",
135
+ )
136
+ )
137
+ }
138
+ .isInstanceOf(IllegalArgumentException::class.java)
139
+ .hasMessage("Invalid transferMethod \"wifi\". Expected auto, direct, or ble.")
140
+ }
111
141
  }
@@ -0,0 +1,22 @@
1
+ package com.mentra.bluetoothsdk.sgcs;
2
+
3
+ import static org.assertj.core.api.Assertions.assertThat;
4
+
5
+ import org.junit.Test;
6
+
7
+ public class Ar99AdvertisementMatcherTest {
8
+
9
+ @Test
10
+ public void supportsOnlyDocumentedProjectIdentifiers() {
11
+ assertThat(Ar99.isSupportedProjectName("AR99")).isTrue();
12
+ assertThat(Ar99.isSupportedProjectName("ar99")).isTrue();
13
+
14
+ assertThat(Ar99.isSupportedProjectName("AF98")).isFalse();
15
+ assertThat(Ar99.isSupportedProjectName("AF99")).isFalse();
16
+ assertThat(Ar99.isSupportedProjectName("HVXM")).isFalse();
17
+ assertThat(Ar99.isSupportedProjectName("HVXF")).isFalse();
18
+ assertThat(Ar99.isSupportedProjectName("AR99X")).isFalse();
19
+ assertThat(Ar99.isSupportedProjectName("")).isFalse();
20
+ assertThat(Ar99.isSupportedProjectName(null)).isFalse();
21
+ }
22
+ }
@@ -135,6 +135,26 @@ public class BleJsonCompactTest {
135
135
  assertEquals("wifi_scan_result", BleJsonCompact.encode(wifiScan).getString("t"));
136
136
  }
137
137
 
138
+ @Test
139
+ public void streamTelemetryRoundTripsWithCompactKeys() throws Exception {
140
+ JSONObject status =
141
+ new JSONObject(
142
+ "{\"type\":\"stream_status\",\"status\":\"streaming\","
143
+ + "\"stats\":{\"bitrate\":912345,\"fps\":19.8,"
144
+ + "\"droppedFrames\":2,\"duration\":31,"
145
+ + "\"temperatureC\":54.6}}\n");
146
+
147
+ JSONObject wire = BleJsonCompact.encode(status);
148
+ assertEquals(912345L, wire.getJSONObject("st").getLong("br"));
149
+ assertEquals(54.6d, wire.getJSONObject("st").getDouble("tc"), 0.001d);
150
+
151
+ JSONObject restored = BleJsonCompact.decode(wire);
152
+ JSONObject stats = restored.getJSONObject("stats");
153
+ assertEquals(19.8d, stats.getDouble("fps"), 0.001d);
154
+ assertEquals(2L, stats.getLong("droppedFrames"));
155
+ assertEquals(31L, stats.getLong("duration"));
156
+ }
157
+
138
158
  @Test
139
159
  public void decodeIfSupported_rejectsCompactLowRoi() throws Exception {
140
160
  JSONObject compactPing = new JSONObject("{\"t\":\"ping\"}");
@@ -0,0 +1,45 @@
1
+ package com.mentra.bluetoothsdk.utils
2
+
3
+ import org.assertj.core.api.Assertions.assertThat
4
+ import org.junit.Test
5
+
6
+ class PhoneAudioMonitorTest {
7
+ @Test
8
+ fun `keeps explicit playback hint while Android reports active media`() {
9
+ assertThat(
10
+ PhoneAudioMonitor.shouldKeepOwnAppAudioSignal(
11
+ signalledPlaying = true,
12
+ systemAudioPlaying = true,
13
+ nowMs = 10_000,
14
+ holdoffUntilMs = 1_000,
15
+ )
16
+ )
17
+ .isTrue()
18
+ }
19
+
20
+ @Test
21
+ fun `keeps explicit playback hint during codec transition holdoff`() {
22
+ assertThat(
23
+ PhoneAudioMonitor.shouldKeepOwnAppAudioSignal(
24
+ signalledPlaying = true,
25
+ systemAudioPlaying = false,
26
+ nowMs = 500,
27
+ holdoffUntilMs = 1_000,
28
+ )
29
+ )
30
+ .isTrue()
31
+ }
32
+
33
+ @Test
34
+ fun `expires stale playback hint when Android media is inactive`() {
35
+ assertThat(
36
+ PhoneAudioMonitor.shouldKeepOwnAppAudioSignal(
37
+ signalledPlaying = true,
38
+ systemAudioPlaying = false,
39
+ nowMs = 1_000,
40
+ holdoffUntilMs = 1_000,
41
+ )
42
+ )
43
+ .isFalse()
44
+ }
45
+ }
@@ -58,7 +58,7 @@ export declare function isConnectedGlassesConnectionStatus(status: GlassesConnec
58
58
  export declare function isReadyGlassesConnectionStatus(status: GlassesConnectionStatus): boolean;
59
59
  export declare function isBusyGlassesConnectionStatus(status: GlassesConnectionStatus): boolean;
60
60
  export declare function createDisconnectedGlassesStatus(): Partial<GlassesStatus>;
61
- /** K900 `sr_getvol` response (Mentra Live glasses media step volume 0–15). */
61
+ /** K900 `sr_getvol` response (Mentra Live glasses media step volume 0闂?5). */
62
62
  export type GlassesMediaVolumeGetResult = {
63
63
  level: number;
64
64
  statusCode: number;
@@ -91,11 +91,11 @@ export type WifiStatusChangeEvent = WifiStatus & {
91
91
  /**
92
92
  * Glasses-reported provisioning failure reason when THIS event is the verdict of a
93
93
  * failed connect attempt; absent on routine link-state updates. An attempt property,
94
- * not a link property — which is why it lives on the event, not on WifiStatus:
94
+ * not a link property 闂?which is why it lives on the event, not on WifiStatus:
95
95
  * "connect_timeout" arrives on a disconnected status (never associated), while
96
96
  * "connected_to_other_network" arrives on a *connected* status (the attempt failed
97
97
  * and the glasses ended up on / fell back to a different SSID than requested).
98
- * Requires ASG client v40+ — older glasses never send it.
98
+ * Requires ASG client v40+ 闂?older glasses never send it.
99
99
  */
100
100
  error?: string;
101
101
  };
@@ -360,16 +360,20 @@ export type RgbLedAction = "on" | "off";
360
360
  export type RgbLedColor = "red" | "green" | "blue" | "orange" | "white";
361
361
  export type PhotoSize = "low" | "medium" | "high" | "max";
362
362
  export type PhotoMode = "photo" | "text";
363
+ export type PhotoTransferMethod = "auto" | "direct" | "ble";
363
364
  export type ButtonPhotoSize = "low" | "medium" | "high" | "max";
364
365
  /**
365
366
  * @deprecated Sticky action-button photo presets via {@link BluetoothSdkPublicModule.setPhotoCaptureDefaults}
366
367
  * are deprecated. Prefer per-request {@link BluetoothSdkPublicModule.requestPhoto} options
367
- * (e.g. `mode: "text"` for AE ÷3) instead of persisting button-photo tuning on the glasses.
368
+ * (e.g. `mode: "text"` for text sensor size/crop, or explicit `aeExposureDivisor`) instead of
369
+ * persisting button-photo tuning on the glasses.
368
370
  */
369
371
  export type PhotoCaptureDefaults = {
370
372
  size?: PhotoSize;
371
- mfnr?: boolean;
373
+ /** ZSL preview buffering for physical camera-button photos. */
372
374
  zsl?: boolean;
375
+ /** MFNR still capture for physical camera-button photos. */
376
+ mfnr?: boolean;
373
377
  noiseReduction?: boolean;
374
378
  edgeEnhancement?: boolean;
375
379
  ispDigitalGain?: number;
@@ -413,6 +417,7 @@ export declare const DeviceModels: {
413
417
  readonly Z100: "Vuzix Z100";
414
418
  readonly Frame: "Brilliant Frame";
415
419
  readonly Nimo: "NIMO";
420
+ readonly Ar99: "AR99";
416
421
  readonly R1: "Even Realities R1";
417
422
  };
418
423
  export type DeviceModel = (typeof DeviceModels)[keyof typeof DeviceModels];
@@ -424,7 +429,7 @@ export type DashboardMenuItem = {
424
429
  };
425
430
  export declare const CAMERA_FOV_MIN = 62;
426
431
  export declare const CAMERA_FOV_MAX = 118;
427
- export declare const CAMERA_FOV_DEFAULT = 102;
432
+ export declare const CAMERA_FOV_DEFAULT = 118;
428
433
  export type CameraRoiPosition = "center" | "bottom" | "top";
429
434
  export type CameraRoiPositionValue = 0 | 1 | 2;
430
435
  export type CameraFovPreset = "narrow" | "standard" | "wide";
@@ -461,8 +466,8 @@ export type PhotoRequestParams = {
461
466
  appId?: string;
462
467
  size: PhotoSize;
463
468
  mode?: PhotoMode;
464
- /** `ble` skips direct upload and forces phone-relayed BLE image transfer. */
465
- transferMethod?: "auto" | "ble";
469
+ /** `direct` disables BLE fallback; `ble` skips direct upload and forces phone-relayed transfer. */
470
+ transferMethod?: PhotoTransferMethod;
466
471
  webhookUrl: string | null;
467
472
  authToken: string | null;
468
473
  compress: PhotoCompression;
@@ -478,8 +483,10 @@ export type PhotoRequestParams = {
478
483
  /** Requested on wire; glasses may log not_implemented. */
479
484
  noiseReduction?: boolean;
480
485
  edgeEnhancement?: boolean;
481
- mfnr?: boolean;
486
+ /** ZSL buffering. Forced off for manual/scan stills because fixed sensor controls take priority. */
482
487
  zsl?: boolean;
488
+ /** MFNR still capture. Forced off for manual/scan stills because fixed sensor controls take priority. */
489
+ mfnr?: boolean;
483
490
  ispDigitalGain?: number;
484
491
  ispAnalogGain?: string;
485
492
  };
@@ -491,6 +498,10 @@ export type WarmUpCameraParams = {
491
498
  exposureTimeNs?: number | null;
492
499
  /** Ready-state hold; defaults to 15 seconds and is capped at 60 seconds by ASG. */
493
500
  durationMs?: number;
501
+ /** ZSL preview buffering for the warm-up session. */
502
+ zsl?: boolean;
503
+ /** MFNR still capture for the warm-up session. */
504
+ mfnr?: boolean;
494
505
  };
495
506
  export type StreamVideoConfig = {
496
507
  width?: number;
@@ -595,7 +606,7 @@ export type StreamResolvedConfig = {
595
606
  noiseSuppression?: boolean;
596
607
  };
597
608
  };
598
- /** Live encoder telemetry, when the glasses emit it (no firmware does yet). */
609
+ /** Live encoder and device telemetry emitted periodically by supported glasses firmware. */
599
610
  export type StreamLiveStats = {
600
611
  /** Current encoded video bitrate in bits per second. */
601
612
  bitrate?: number;
@@ -604,7 +615,7 @@ export type StreamLiveStats = {
604
615
  droppedFrames?: number;
605
616
  /** Seconds since the stream started. */
606
617
  duration?: number;
607
- /** Device temperature in °C, if the hardware reports it. */
618
+ /** Device temperature in 闂佺娅i悡? if the hardware reports it. */
608
619
  temperatureC?: number;
609
620
  };
610
621
  type StreamStatusCommon = {
@@ -653,6 +664,16 @@ export type MtkUpdateCompleteEvent = {
653
664
  message: string;
654
665
  timestamp: number;
655
666
  };
667
+ /**
668
+ * The glasses process restarted while the BES kept the BLE link alive (its `sid`
669
+ * changed, or first appeared after an update from a pre-sid build). There is no
670
+ * physical disconnect for this — treat it as the logical reconnect edge.
671
+ */
672
+ export type GlassesSessionChangedEvent = {
673
+ type: "glasses_session_changed";
674
+ previous_sid: string;
675
+ sid: string;
676
+ };
656
677
  /** @deprecated Glasses no longer emit ota_progress; use {@link OtaStatusEvent} and status-store mapping. */
657
678
  export type OtaProgressEvent = {
658
679
  type: "ota_progress";
@@ -668,14 +689,6 @@ export type OtaStartAckEvent = {
668
689
  type: "ota_start_ack";
669
690
  timestamp: number;
670
691
  };
671
- export type OtaUpdateAvailableEvent = {
672
- type: "ota_update_available";
673
- version_code?: number;
674
- version_name?: string;
675
- updates?: string[];
676
- total_size?: number;
677
- cache_ready?: boolean;
678
- };
679
692
  export type OtaStatusEvent = {
680
693
  type: "ota_status";
681
694
  session_id: string;
@@ -749,9 +762,10 @@ export type BluetoothSdkModuleEvents = {
749
762
  stream_status: (event: StreamStatusEvent) => void;
750
763
  keep_alive_ack: (event: KeepAliveAckEvent) => void;
751
764
  mtk_update_complete: (event: MtkUpdateCompleteEvent) => void;
752
- ota_update_available: (event: OtaUpdateAvailableEvent) => void;
765
+ glasses_session_changed: (event: GlassesSessionChangedEvent) => void;
753
766
  ota_start_ack: (event: OtaStartAckEvent) => void;
754
767
  ota_status: (event: OtaStatusEvent) => void;
768
+ ar99_ota_status: (event: Ar99OtaStatusEvent) => void;
755
769
  version_info: (event: VersionInfoEvent) => void;
756
770
  send_command_to_ble: (event: BleCommandTraceEvent) => void;
757
771
  receive_command_from_ble: (event: BleCommandTraceEvent) => void;
@@ -763,6 +777,15 @@ export interface ExtractionProgressEvent {
763
777
  bytesRead: number;
764
778
  totalBytes: number;
765
779
  }
780
+ export interface Ar99OtaStatusEvent {
781
+ type: "ar99_ota_status";
782
+ phase: string;
783
+ progress: number;
784
+ offset: number;
785
+ total: number;
786
+ errorMessage?: string;
787
+ error_message?: string;
788
+ }
766
789
  export interface PhoneNotificationEvent {
767
790
  notificationId: string;
768
791
  app: string;
@@ -818,9 +841,9 @@ export type BluetoothSdkEventMap = {
818
841
  mic_pcm: MicPcmEvent;
819
842
  mic_lc3: MicLc3Event;
820
843
  stream_status: StreamStatusEvent;
821
- ota_update_available: OtaUpdateAvailableEvent;
822
844
  ota_start_ack: OtaStartAckEvent;
823
845
  ota_status: OtaStatusEvent;
846
+ ar99_ota_status: Ar99OtaStatusEvent;
824
847
  version_info: VersionInfoEvent;
825
848
  extraction_progress: ExtractionProgressEvent;
826
849
  };
@@ -857,9 +880,10 @@ export interface BluetoothSdkPublicModule {
857
880
  setHotspotState(enabled: boolean): Promise<HotspotStatusChangeEvent>;
858
881
  setGalleryModeEnabled(enabled: boolean): Promise<SettingsAckSuccessEvent>;
859
882
  setVoiceActivityDetectionEnabled(enabled: boolean): Promise<void>;
883
+ setLoudnessGateEnabled(enabled: boolean): Promise<void>;
860
884
  /**
861
885
  * @deprecated Sticky action-button photo presets are deprecated. Prefer per-request
862
- * `requestPhoto(...)` options (e.g. `mode: "text"` for AE ÷3, or explicit per-shot
886
+ * `requestPhoto(...)` options (e.g. `mode: "text"` for text sensor size/crop, or explicit per-shot
863
887
  * fields). Still functional until removed in a future release.
864
888
  */
865
889
  setPhotoCaptureDefaults(settings: PhotoCaptureDefaults): Promise<SettingsAckSuccessEvent>;
@@ -894,7 +918,7 @@ export interface BluetoothSdkPublicModule {
894
918
  * Stop the active recording. When {@link webhookUrl} is provided, the glasses
895
919
  * upload the recorded video to it (multipart) using {@link authToken}. These
896
920
  * are supplied at stop time (not start) so the token is fresh when the upload
897
- * runs — a recording can last arbitrarily long. An empty/omitted webhook keeps
921
+ * runs 闂?a recording can last arbitrarily long. An empty/omitted webhook keeps
898
922
  * the video on device (no upload).
899
923
  */
900
924
  stopVideoRecording(requestId: string, webhookUrl?: string, authToken?: string): Promise<VideoRecordingStoppedStatusEvent>;
@@ -911,6 +935,10 @@ export interface BluetoothSdkPublicModule {
911
935
  checkForOtaUpdate(): Promise<boolean>;
912
936
  /** Start the OTA flow with the same configured manifest URL used by checkForOtaUpdate(). */
913
937
  startOtaUpdate(): Promise<OtaStartAckEvent>;
938
+ startAr99OtaFromFile(path: string): Promise<boolean>;
939
+ cancelAr99Ota(): Promise<void>;
940
+ sendAr99FactoryReset(): Promise<void>;
941
+ buildAr99OtaSignature(secret: string, appName: string, currentVersion: string, serialNumber: string, nonce: string): string;
914
942
  setSttModelDetails(path: string, languageCode: string): Promise<void>;
915
943
  getSttModelPath(): Promise<string>;
916
944
  checkSttModelAvailable(): Promise<boolean>;
@@ -944,6 +972,8 @@ export interface OtaUpdateInfo {
944
972
  updates: string[];
945
973
  totalSize: number;
946
974
  cacheReady?: boolean;
975
+ /** True when the APK step installs an older build than the glasses currently run (exact-pin manifests only). */
976
+ isDowngrade?: boolean;
947
977
  }
948
978
  export interface OtaProgress {
949
979
  stage: OtaStage;
@@ -1015,13 +1045,15 @@ export interface Device {
1015
1045
  /**
1016
1046
  * Stable app-facing key for this scan result, within the limits of the
1017
1047
  * platform identifier available to the SDK. Do not parse this value; use the
1018
- * typed model, name, address, and rssi fields instead.
1048
+ * typed model, name, address, projectName, and rssi fields instead.
1019
1049
  */
1020
1050
  id: string;
1021
1051
  model: DeviceModel;
1022
1052
  name: string;
1023
1053
  /** Platform address/identifier when available: Android Bluetooth address, iOS CoreBluetooth identifier. */
1024
1054
  address?: string;
1055
+ /** Optional AR99 project discriminator. Supported value: AR99. */
1056
+ projectName?: string;
1025
1057
  /**
1026
1058
  * Optional scan signal strength. It may be undefined at first discovery and
1027
1059
  * appear in a later scan update when the platform reports RSSI metadata.
@@ -1047,7 +1079,7 @@ export interface WifiSearchResult {
1047
1079
  ssid: string;
1048
1080
  requiresPassword: boolean;
1049
1081
  signalStrength: number;
1050
- /** Frequency in MHz (from glasses scan). 5 GHz band is typically 5170–5825. Omitted if unknown. */
1082
+ /** Frequency in MHz (from glasses scan). 5 GHz band is typically 5170闂?825. Omitted if unknown. */
1051
1083
  frequency?: number;
1052
1084
  }
1053
1085
  export interface BluetoothStatus {
@@ -1096,6 +1128,7 @@ export type BluetoothSettingsUpdate = Partial<{
1096
1128
  twelve_hour_time: boolean;
1097
1129
  gallery_mode: boolean;
1098
1130
  voice_activity_detection_enabled: boolean;
1131
+ loudness_gate_enabled: boolean;
1099
1132
  button_photo_size: ButtonPhotoSize;
1100
1133
  button_video_settings: {
1101
1134
  width: number;