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

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 +41 -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 +71 -7
  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
@@ -95,6 +95,7 @@ class MentraLive : SGCManager() {
95
95
  // LC3 frame size for Mentra Live
96
96
  private const val LC3_FRAME_SIZE = 40
97
97
  private const val VOICE_ACTIVITY_DETECTION_SWITCH_TYPE = 8
98
+ private const val LOUDNESS_GATE_SWITCH_TYPE = 10
98
99
  private const val BES2700_MTU_LIMIT = 509
99
100
 
100
101
  // L2CAP CoC fast path: new BES2700 firmware registers an LE L2CAP CoC server on this
@@ -233,6 +234,15 @@ class MentraLive : SGCManager() {
233
234
  private var peerK900Le = false
234
235
  private var peerWireCapsBinary = false
235
236
  private var peerFilePayloadV2 = false
237
+ // Last observed glasses process session id (`sid` in glasses_ready / version_info_1).
238
+ // The BES keeps the BLE link alive across asg_client restarts, so transport state
239
+ // cannot signal a restart - a CHANGED (or newly appearing) sid is the restart signal.
240
+ // Null = no sid observed this BLE session (legacy glasses, or none seen yet).
241
+ private var glassesSessionId: String? = null
242
+ // True once a glasses_ready completed on THIS physical BLE session; resets only with
243
+ // the physical connection (never on heartbeat readiness flaps), so a first-seen sid
244
+ // after an upgrade OTA is always detected as a restart.
245
+ private var readinessCompletedThisBleSession = false
236
246
  // Note: appVersion, buildNumber, deviceModel, androidVersion
237
247
  // are inherited from SGCManager parent class
238
248
 
@@ -806,6 +816,15 @@ class MentraLive : SGCManager() {
806
816
  return
807
817
  }
808
818
 
819
+ if (state == ConnTypes.DISCONNECTED) {
820
+ // A manufacturing serial is session-bound. Clear it on disconnect so a previous
821
+ // pair's serial can never be associated with the next connection. Connect must NOT
822
+ // clear it: DeviceManager.disconnect already wipes it before any new connection, and
823
+ // clearing on CONNECTED would wipe a still-valid serial mid-session when a same-link
824
+ // glasses_ready (e.g. ASG restart) re-publishes CONNECTED.
825
+ DeviceStore.apply("glasses", "serialNumber", "")
826
+ }
827
+
809
828
  // Actually update the connection state!
810
829
  DeviceStore.apply("glasses", "connectionState", state)
811
830
 
@@ -821,6 +840,10 @@ class MentraLive : SGCManager() {
821
840
  DeviceStore.apply("glasses", "appVersion", "")
822
841
  DeviceStore.apply("glasses", "besFirmwareVersion", "")
823
842
  DeviceStore.apply("glasses", "mtkFirmwareVersion", "")
843
+ // Modern ASG builds omit ota_version_url entirely (the phone owns manifest
844
+ // selection), and the chunked parser only writes present fields — clear it here so
845
+ // a URL reported by a previous build cannot leak into this session.
846
+ DeviceStore.apply("glasses", "otaVersionUrl", "")
824
847
  Bridge.log("LIVE: Cleared cached version_info fields for fresh session")
825
848
  }
826
849
 
@@ -1561,6 +1584,8 @@ class MentraLive : SGCManager() {
1561
1584
 
1562
1585
  connectedDevice = null
1563
1586
  glassesReady = false // Reset ready state on disconnect
1587
+ glassesSessionId = null // Fresh BLE session starts with no sid known
1588
+ readinessCompletedThisBleSession = false
1564
1589
 
1565
1590
  // Reset audio pairing flags
1566
1591
  glassesReadyReceived = false
@@ -1625,6 +1650,8 @@ class MentraLive : SGCManager() {
1625
1650
  isConnected = false
1626
1651
  isConnecting = false
1627
1652
  glassesReady = false
1653
+ glassesSessionId = null
1654
+ readinessCompletedThisBleSession = false
1628
1655
  glassesReadyReceived = false
1629
1656
  audioConnected = false
1630
1657
 
@@ -3515,6 +3542,11 @@ class MentraLive : SGCManager() {
3515
3542
  resetWireNegotiationState()
3516
3543
  parsePeerWireCaps(json)
3517
3544
  maybeSendWireHandshake()
3545
+ // Record the session id BEFORE marking ready: an unsolicited glasses_ready
3546
+ // already runs this full remote-reset flow, so recording (not re-triggering)
3547
+ // is correct here; version_info detection covers the restart case.
3548
+ json.optString("sid", "").takeIf { it.isNotEmpty() }?.let { glassesSessionId = it }
3549
+ readinessCompletedThisBleSession = true
3518
3550
 
3519
3551
  // Set the ready flag to stop any future readiness checks
3520
3552
  glassesReady = true
@@ -3674,7 +3706,7 @@ class MentraLive : SGCManager() {
3674
3706
 
3675
3707
  "version_info" -> {
3676
3708
  // Process version information from ASG client (legacy single-message format)
3677
- Bridge.log("LIVE: Received version info from ASG client: " + json.toString())
3709
+ Bridge.log("LIVE: Received version info from ASG client")
3678
3710
 
3679
3711
  // Extract version information
3680
3712
  val appVersionLegacy = json.optString("app_version", "")
@@ -3684,6 +3716,7 @@ class MentraLive : SGCManager() {
3684
3716
  val otaVersionUrlLegacy: String? = json.optString("ota_version_url", null)
3685
3717
  val firmwareVersionLegacy = json.optString("firmware_version", "")
3686
3718
  val btMacAddressLegacy = json.optString("bt_mac_address", "")
3719
+ val serialNumberLegacy = json.optString("serial_number", "")
3687
3720
 
3688
3721
  // Update parent SGCManager fields
3689
3722
  DeviceStore.apply("glasses", "appVersion", appVersionLegacy)
@@ -3697,6 +3730,9 @@ class MentraLive : SGCManager() {
3697
3730
  )
3698
3731
  DeviceStore.apply("glasses", "firmwareVersion", firmwareVersionLegacy)
3699
3732
  DeviceStore.apply("glasses", "bluetoothMacAddress", btMacAddressLegacy)
3733
+ if (serialNumberLegacy.isNotBlank()) {
3734
+ DeviceStore.apply("glasses", "serialNumber", serialNumberLegacy)
3735
+ }
3700
3736
 
3701
3737
  val versionInfoLegacy = HashMap<String, Any>()
3702
3738
  versionInfoLegacy["appVersion"] = appVersionLegacy
@@ -3878,7 +3914,7 @@ class MentraLive : SGCManager() {
3878
3914
  else -> {
3879
3915
  // Flexible version_info parsing - handle any version_info* message
3880
3916
  if (type.startsWith("version_info")) {
3881
- Bridge.log("LIVE: Received " + type + ": " + json.toString())
3917
+ Bridge.log("LIVE: Received " + type)
3882
3918
 
3883
3919
  // Extract all fields from JSON (except "type")
3884
3920
  val fields = HashMap<String, Any>()
@@ -3965,6 +4001,10 @@ class MentraLive : SGCManager() {
3965
4001
  fields["bt_mac_address"] as String
3966
4002
  )
3967
4003
  }
4004
+ val serialNumber = fields["serial_number"] as? String
4005
+ if (!serialNumber.isNullOrBlank()) {
4006
+ DeviceStore.apply("glasses", "serialNumber", serialNumber)
4007
+ }
3968
4008
  if (fields.containsKey("system_time_ms")) {
3969
4009
  val v = fields["system_time_ms"]
3970
4010
  if (v is Number) {
@@ -3981,6 +4021,7 @@ class MentraLive : SGCManager() {
3981
4021
  // running them per chunk is safe.
3982
4022
  parsePeerWireCaps(json)
3983
4023
  maybeSendWireHandshake()
4024
+ handleGlassesSessionId(json)
3984
4025
 
3985
4026
  Bridge.log("LIVE: Processed version_info fields and sent to RN")
3986
4027
  Bridge.sendVersionInfo(fields)
@@ -4332,15 +4373,7 @@ class MentraLive : SGCManager() {
4332
4373
  // The glassesReady flag is reset on disconnect/reconnect, so this won't
4333
4374
  // prevent proper reconnection
4334
4375
  if (!glassesReady) {
4335
- Bridge.log(
4336
- "LIVE: 📱 Sending phone_ready to glasses - waiting for glasses_ready response"
4337
- )
4338
- val readyMsg = JSONObject()
4339
- readyMsg.put("type", "phone_ready")
4340
- readyMsg.put("timestamp", System.currentTimeMillis())
4341
-
4342
- // Send it through our data channel
4343
- sendJson(readyMsg, true)
4376
+ sendPhoneReady("SOC ready")
4344
4377
  } else {
4345
4378
  Bridge.log(
4346
4379
  "LIVE: ✅ Glasses already marked as ready, skipping phone_ready"
@@ -4421,6 +4454,8 @@ class MentraLive : SGCManager() {
4421
4454
  updateConnectionState(ConnTypes.DISCONNECTED)
4422
4455
  glassesReady = false
4423
4456
  glassesReadyReceived = false
4457
+ glassesSessionId = null
4458
+ readinessCompletedThisBleSession = false
4424
4459
  }
4425
4460
  "sr_adota" -> {
4426
4461
  // BES chip OTA progress — K900 path (serial busy during BES flash). Emit ota_status
@@ -5542,6 +5577,8 @@ class MentraLive : SGCManager() {
5542
5577
  mode: PhotoMode = PhotoMode.PHOTO,
5543
5578
  exposureTimeNs: Long?,
5544
5579
  durationMs: Int,
5580
+ zsl: Boolean? = null,
5581
+ mfnr: Boolean? = null,
5545
5582
  ) {
5546
5583
  Bridge.log(
5547
5584
  "LIVE: warmUpCamera() entry — requestId=" +
@@ -5564,6 +5601,12 @@ class MentraLive : SGCManager() {
5564
5601
  if (exposureTimeNs != null && exposureTimeNs > 0L) {
5565
5602
  json.put("exposureTimeNs", exposureTimeNs)
5566
5603
  }
5604
+ if (mfnr != null) {
5605
+ json.put("mfnr", mfnr)
5606
+ }
5607
+ if (zsl != null) {
5608
+ json.put("zsl", zsl)
5609
+ }
5567
5610
  json.put("durationMs", if (durationMs > 0) durationMs else 15000)
5568
5611
  sendJson(json, true)
5569
5612
  } catch (e: JSONException) {
@@ -7249,6 +7292,62 @@ class MentraLive : SGCManager() {
7249
7292
  wireHandshakeSentGeneration = -1
7250
7293
  }
7251
7294
 
7295
+ /** Sends phone_ready to (re)start the glasses-driven readiness flow. */
7296
+ private fun sendPhoneReady(reason: String) {
7297
+ Bridge.log("LIVE: 📱 Sending phone_ready to glasses ($reason) - waiting for glasses_ready response")
7298
+ val readyMsg = JSONObject()
7299
+ readyMsg.put("type", "phone_ready")
7300
+ readyMsg.put("timestamp", System.currentTimeMillis())
7301
+ sendJson(readyMsg, true)
7302
+ }
7303
+
7304
+ /**
7305
+ * Tracks the glasses process session id (`sid`) and re-runs the phone-driven readiness
7306
+ * flow when it changes under a live BLE link. The BES holds the link across asg_client
7307
+ * restarts (APK OTA, crash recovery), so a restarted glasses process is invisible to
7308
+ * transport state; the sid is the explicit protocol signal.
7309
+ *
7310
+ * Tri-state by design (retro-compat with pre-sid glasses builds):
7311
+ * - no `sid` key: legacy glasses - do nothing, today's behavior;
7312
+ * - sid appears where none was known on an ESTABLISHED session: the glasses just
7313
+ * updated from a pre-sid build and restarted - treat as a restart (this is the
7314
+ * upgrade OTA itself);
7315
+ * - sid differs from the recorded one: the glasses restarted - treat as a restart;
7316
+ * - same sid: same process, no action.
7317
+ *
7318
+ * A restart is handled as a LOGICAL session reset: send phone_ready immediately
7319
+ * (instead of waiting for the next sr_hrt heartbeat, and bypassing its stale
7320
+ * glassesReady suppression); the returning glasses_ready runs the full existing
7321
+ * remote-wire-reset flow. glassesReady/fullyBooted are deliberately left untouched -
7322
+ * the physical link never dropped, so the connection UI must not flap. The RN layer
7323
+ * is notified so the OTA coordinator can treat it as its reconnect edge.
7324
+ */
7325
+ private fun handleGlassesSessionId(json: JSONObject) {
7326
+ val sid = json.optString("sid", "")
7327
+ if (sid.isEmpty()) return
7328
+ val previous = glassesSessionId
7329
+ if (sid == previous) return
7330
+ glassesSessionId = sid
7331
+ if (previous == null && !readinessCompletedThisBleSession) {
7332
+ // First sid of a fresh BLE session before readiness completes: the normal
7333
+ // pairing/readiness flow is already running - just record it.
7334
+ return
7335
+ }
7336
+ Bridge.log(
7337
+ "LIVE: 🔁 Glasses session changed (${previous ?: "<pre-sid build>"} -> $sid) - " +
7338
+ "asg restarted under a live link, re-running readiness"
7339
+ )
7340
+ sendPhoneReady("glasses session changed")
7341
+ try {
7342
+ val payload = HashMap<String, Any>()
7343
+ payload["previous_sid"] = previous ?: ""
7344
+ payload["sid"] = sid
7345
+ Bridge.sendTypedMessage("glasses_session_changed", payload)
7346
+ } catch (e: Exception) {
7347
+ Log.e(TAG, "Error emitting glasses_session_changed", e)
7348
+ }
7349
+ }
7350
+
7252
7351
  private fun parsePeerWireCaps(json: JSONObject) {
7253
7352
  val caps = json.optJSONObject("wire_caps") ?: return
7254
7353
  if (caps.optBoolean("k900_le", false)) {
@@ -8887,6 +8986,9 @@ class MentraLive : SGCManager() {
8887
8986
 
8888
8987
  // Send glasses-side Voice Activity Detection setting.
8889
8988
  sendVoiceActivityDetectionSetting()
8989
+
8990
+ // Send glasses-side loudness / Barrier gate setting.
8991
+ sendLoudnessGateSetting()
8890
8992
  }
8891
8993
 
8892
8994
  override fun sendVoiceActivityDetectionSetting() {
@@ -8929,11 +9031,56 @@ class MentraLive : SGCManager() {
8929
9031
  }
8930
9032
  }
8931
9033
 
9034
+ override fun sendLoudnessGateSetting() {
9035
+ val value = DeviceStore.get("bluetooth", "loudness_gate_enabled")
9036
+ val enabled =
9037
+ if (value is Boolean) value
9038
+ else BluetoothSdkDefaults.LOUDNESS_GATE_ENABLED
9039
+
9040
+ Bridge.log("LIVE: 🎚️ Sending loudness/Barrier gate setting to glasses: " + enabled)
9041
+
9042
+ if (!isConnected) {
9043
+ Bridge.log("LIVE: Cannot send loudness gate setting - not connected")
9044
+ return
9045
+ }
9046
+
9047
+ try {
9048
+ val body = JSONObject()
9049
+ body.put("type", LOUDNESS_GATE_SWITCH_TYPE)
9050
+ body.put("switch", if (enabled) 1 else 0)
9051
+
9052
+ val cmdObject = JSONObject()
9053
+ cmdObject.put("C", "cs_swit")
9054
+ cmdObject.put("V", 1)
9055
+ cmdObject.put("B", body.toString())
9056
+
9057
+ val packedData =
9058
+ K900ProtocolUtils.packDataToK900(
9059
+ cmdObject.toString().toByteArray(StandardCharsets.UTF_8),
9060
+ K900ProtocolUtils.CMD_TYPE_STRING,
9061
+ k900LengthEndian()
9062
+ )
9063
+ if (packedData == null) {
9064
+ Bridge.log("LIVE: Failed to pack loudness gate setting command")
9065
+ return
9066
+ }
9067
+ queueData(packedData)
9068
+ } catch (e: JSONException) {
9069
+ Log.e(TAG, "Error creating loudness gate setting command", e)
9070
+ }
9071
+ }
9072
+
8932
9073
  /** Send button photo settings to glasses, replaying all stored scan-tuning fields. */
8933
9074
  override fun sendButtonPhotoSettings() {
9075
+ val legacyZslMfnr =
9076
+ DeviceStore.get(ObservableStore.BLUETOOTH_CATEGORY, "button_photo_zsl_mfnr") as Boolean?
8934
9077
  val size = DeviceStore.get(ObservableStore.BLUETOOTH_CATEGORY, "button_photo_size") as String?
8935
- val mfnr = DeviceStore.get(ObservableStore.BLUETOOTH_CATEGORY, "button_photo_mfnr") as Boolean?
8936
- val zsl = DeviceStore.get(ObservableStore.BLUETOOTH_CATEGORY, "button_photo_zsl") as Boolean?
9078
+ val mfnr =
9079
+ (DeviceStore.get(ObservableStore.BLUETOOTH_CATEGORY, "button_photo_mfnr") as Boolean?)
9080
+ ?: legacyZslMfnr
9081
+ val zsl =
9082
+ (DeviceStore.get(ObservableStore.BLUETOOTH_CATEGORY, "button_photo_zsl") as Boolean?)
9083
+ ?: legacyZslMfnr
8937
9084
  val noiseReduction = DeviceStore.get(ObservableStore.BLUETOOTH_CATEGORY, "button_photo_noise_reduction") as Boolean?
8938
9085
  val edgeEnhancement = DeviceStore.get(ObservableStore.BLUETOOTH_CATEGORY, "button_photo_edge_enhancement") as Boolean?
8939
9086
  val ispDigitalGain = DeviceStore.get(ObservableStore.BLUETOOTH_CATEGORY, "button_photo_isp_digital_gain") as Int?
@@ -8950,7 +9097,7 @@ class MentraLive : SGCManager() {
8950
9097
 
8951
9098
  /** Send camera FOV setting to glasses (K900 / Mentra Live). */
8952
9099
  override fun sendCameraFovSetting() {
8953
- var fov = 102
9100
+ var fov = 118
8954
9101
  var roiPosition = 0
8955
9102
  try {
8956
9103
  val raw = DeviceStore.get("bluetooth", "camera_fov")
@@ -321,6 +321,9 @@ abstract class SGCManager {
321
321
  // Voice Activity Detection
322
322
  open fun sendVoiceActivityDetectionSetting() {}
323
323
 
324
+ // Mentra Live center-mic loudness / Barrier gate
325
+ open fun sendLoudnessGateSetting() {}
326
+
324
327
  // Start/stop LC3 audio playback from glasses based on the nex_lc3_audio_playback flag.
325
328
  open fun applyNexAudioPlaybackSetting() {}
326
329