@mentra/bluetooth-sdk 3.2.0-dev.265 → 3.2.1-beta.312

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 (54) hide show
  1. package/README.md +28 -0
  2. package/android/lc3Lib/build.gradle +5 -0
  3. package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +35 -0
  4. package/android/src/main/java/com/mentra/bluetoothsdk/Bridge.kt +34 -0
  5. package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +30 -0
  6. package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +20 -1
  7. package/android/src/main/java/com/mentra/bluetoothsdk/GeneratedChangelogCatalog.kt +3 -2
  8. package/android/src/main/java/com/mentra/bluetoothsdk/GeneratedReleaseMetadata.kt +6 -6
  9. package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +12 -0
  10. package/android/src/main/java/com/mentra/bluetoothsdk/events/BluetoothEvents.kt +7 -0
  11. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Mach1.kt +4 -5
  12. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.kt +345 -0
  13. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/SGCManager.kt +13 -0
  14. package/build/BluetoothSdk.types.d.ts +93 -1
  15. package/build/BluetoothSdk.types.d.ts.map +1 -1
  16. package/build/BluetoothSdk.types.js.map +1 -1
  17. package/build/_private/BluetoothSdkModule.d.ts +32 -1
  18. package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
  19. package/build/_private/BluetoothSdkModule.js +15 -1
  20. package/build/_private/BluetoothSdkModule.js.map +1 -1
  21. package/build/generated/changelogCatalog.d.ts +5 -2
  22. package/build/generated/changelogCatalog.d.ts.map +1 -1
  23. package/build/generated/changelogCatalog.js +6 -2
  24. package/build/generated/changelogCatalog.js.map +1 -1
  25. package/build/generated/releaseMetadata.js +6 -6
  26. package/build/generated/releaseMetadata.js.map +1 -1
  27. package/build/index.d.ts.map +1 -1
  28. package/build/index.js +1 -0
  29. package/build/index.js.map +1 -1
  30. package/ios/BluetoothSdkModule.swift +60 -0
  31. package/ios/Source/BluetoothSdkDefaults.swift +1 -1
  32. package/ios/Source/Bridge.swift +27 -0
  33. package/ios/Source/DeviceManager.swift +52 -50
  34. package/ios/Source/DeviceStore.swift +6 -0
  35. package/ios/Source/GeneratedChangelogCatalog.swift +3 -2
  36. package/ios/Source/GeneratedReleaseMetadata.swift +6 -6
  37. package/ios/Source/MentraBluetoothSDK.swift +11 -0
  38. package/ios/Source/audio/PcmStreamPlayer.swift +12 -0
  39. package/ios/Source/events/BluetoothEvents.swift +15 -0
  40. package/ios/Source/services/PhoneMic.swift +1 -3
  41. package/ios/Source/sgcs/MentraLive.swift +247 -1
  42. package/ios/Source/sgcs/SGCManager.swift +32 -0
  43. package/ios/Source/utils/AudioRouteObservation.swift +54 -0
  44. package/ios/Source/utils/AudioSessionMonitor.swift +20 -6
  45. package/ios/Tests/AudioRouteObservationTests.swift +142 -0
  46. package/ios/Tests/BluetoothAudioRouteTests.swift +37 -0
  47. package/ios/Tests/GalleryServerCommandTests.swift +87 -0
  48. package/ios/Tests/WearTuningDispatchTests.swift +51 -0
  49. package/package.json +2 -2
  50. package/src/BluetoothSdk.types.ts +99 -0
  51. package/src/_private/BluetoothSdkModule.ts +64 -1
  52. package/src/generated/changelogCatalog.ts +6 -2
  53. package/src/generated/releaseMetadata.ts +6 -6
  54. package/src/index.ts +1 -0
package/README.md CHANGED
@@ -478,6 +478,34 @@ await sdk.setDashboardContent("")
478
478
 
479
479
  Settings commands that return `SettingsAckSuccessEvent` reject when the ASG reports an error ack. The SDK updates its local settings store only after that ASG ack resolves successfully, so observed SDK state reflects the acknowledged glasses state rather than a queued request. Raw `settings_ack` listener events still use `SettingsAckEvent` because they can include both success and failure statuses. `rgbLedControl(...)` resolves from a successful ASG `rgb_led_control_response` and rejects when the ASG reports `state: "error"`; raw `settings_ack` and `rgb_led_control_response` events remain available through listeners.
480
480
 
481
+ ### Persistent HTTP gallery server (Mentra Live)
482
+
483
+ Use `setGalleryServerEnabled(true)` to keep the existing camera/gallery HTTP server available over the Wi-Fi network the glasses have already joined. This opt-in defaults off and persists across BLE reconnects and glasses restarts until explicitly disabled. It has no idle timeout and does not start or join a hotspot.
484
+
485
+ **This server uses HTTP without authentication. Any client that can reach the glasses can use the full camera/gallery API, including capture and deletion, while it is enabled.** Use it only where your network policy permits that access.
486
+
487
+ ```ts
488
+ const server = await BluetoothSdk.setGalleryServerEnabled(true)
489
+ if (server.listening && server.url) {
490
+ const gallery = await fetch(`${server.url}/api/gallery`).then((response) => response.json())
491
+ console.log(gallery)
492
+ // Download using /api/download?file=<URL-encoded gallery filename>.
493
+ }
494
+
495
+ // Leave enabled for persistent access, or explicitly turn it off:
496
+ await BluetoothSdk.setGalleryServerEnabled(false)
497
+ ```
498
+
499
+ Native Swift: `try await sdk.setGalleryServerEnabled(true)`; Kotlin: `sdk.setGalleryServerEnabled(true)`. Both return `SettingsAckEvent`, with `enabled`, `listening`, and `url` properties. React Native returns `SettingsAckSuccessEvent` with the same fields.
500
+
501
+ The acknowledgement confirms the saved setting. `enabled:true, listening:false` means the setting is enabled but no station endpoint is currently available, for example before Wi-Fi connects or while a bind is being retried. `url` is present only when the server is running and station Wi-Fi has an address. Repeat the same enable call to obtain a fresh endpoint without restarting a healthy server.
502
+
503
+ The same server listens on port 8089 on all local interfaces in persistent mode. Its complete HTTP API is unchanged: listing, downloads, sync, deletion, restore, camera control, and browser UI all work as on the hotspot. Existing response formats, resumable downloads, and in-progress-media filtering remain intact. No HTTP client changes are needed beyond choosing the site-network URL.
504
+
505
+ Disabling restores normal hotspot-only behavior: the server rebinds to an active hotspot address or stops if no hotspot is active. Changing binding modes can interrupt in-flight requests, so switch modes between transfers or retry interrupted requests.
506
+
507
+ Both the native SDK and ASG client need this command; older glasses firmware times out without claiming success. The client app must have local-network access and permit cleartext HTTP to the local device under its platform/MDM policy.
508
+
481
509
  WiFi, hotspot, and version-info commands resolve from the ASG response path, not local dispatch:
482
510
  `requestWifiScan()` resolves from the ASG `wifi_scan_result` completion response with the updated scan list, including `[]` when no networks are found. Intermediate `wifi_scan_result` events can arrive with `scanComplete: false` while the glasses stream discovered networks; the final event uses `scanComplete: true`. If older glasses stream non-empty scan results but never send the completion event, the request resolves with the accumulated scan list when the request times out. `sendWifiCredentials()` resolves when the requested SSID is connected, `forgetWifiNetwork()` returns a semantic `WifiForgetResult`, `getSavedWifiNetworks()` returns a semantic `SavedWifiNetworksResult`, `setHotspotState()` resolves when the requested hotspot state is reported, and `requestVersionInfo()` waits for all chunks declared by the correlated response's `chunkCount`, `chunkIndex`, and `final` metadata. Legacy single-message responses complete immediately; legacy chunked responses complete on `version_info_3` after `version_info_1`. Missing final chunks time out rather than returning partial data after a quiet period.
483
511
 
@@ -88,6 +88,11 @@ android {
88
88
  namespace "com.mentra.lc3Lib"
89
89
  compileSdk 33
90
90
 
91
+ // Reuse the host's NDK instead of downloading AGP's default alongside it.
92
+ if (rootProject.ext.has('ndkVersion')) {
93
+ ndkVersion rootProject.ext.ndkVersion
94
+ }
95
+
91
96
  publishing {
92
97
  singleVariant("release") {
93
98
  withSourcesJar()
@@ -368,6 +368,10 @@ class BluetoothSdkModule : Module() {
368
368
  "receive_command_from_ble",
369
369
  "swipe_volume_status",
370
370
  "switch_status",
371
+ "mic_tuning_state",
372
+ "mic_rms",
373
+ "wear_state",
374
+ "wear_tuning",
371
375
  "rgb_led_control_response",
372
376
  "settings_ack",
373
377
  "version_info",
@@ -582,6 +586,33 @@ class BluetoothSdkModule : Module() {
582
586
  // Stub on Android — iOS uses this for the jetsam stress test.
583
587
  Function("getMemoryMB") { -> 0.0 }
584
588
 
589
+ // MARK: - Mic tuning (internal SDK surface only)
590
+
591
+ AsyncFunction("setMicRmsTelemetry") { enabled: Boolean ->
592
+ deviceManager?.sgc?.setMicRmsTelemetry(enabled)
593
+ }
594
+
595
+ // Fire-and-forget: the answer arrives as a mic_tuning_state event, which
596
+ // the screen is subscribed to anyway.
597
+ AsyncFunction("requestMicTuningState") { deviceManager?.sgc?.requestMicTuningState() }
598
+
599
+ // MARK: - Wear detection (internal SDK surface only)
600
+
601
+ // All fire-and-forget; answers arrive as wear_state / wear_tuning events.
602
+ AsyncFunction("queryWearState") { deviceManager?.queryWearState() }
603
+
604
+ AsyncFunction("setWearReporting") { enabled: Boolean ->
605
+ deviceManager?.setWearReporting(enabled)
606
+ }
607
+
608
+ AsyncFunction("setWearTuning") { intervalMs: Int, count: Int, majority: Int ->
609
+ deviceManager?.setWearTuning(intervalMs, count, majority)
610
+ }
611
+
612
+ AsyncFunction("requestWearTuning") { deviceManager?.requestWearTuning() }
613
+
614
+ AsyncFunction("resetWearTuning") { deviceManager?.resetWearTuning() }
615
+
585
616
  // MARK: - Incident Reporting
586
617
 
587
618
  SdkAsyncFunction("sendIncidentId") { incidentId: String, apiBaseUrl: String? ->
@@ -629,6 +660,10 @@ class BluetoothSdkModule : Module() {
629
660
 
630
661
  // MARK: - Gallery Commands
631
662
 
663
+ SdkCoroutineFunction("setGalleryServerEnabled") { enabled: Boolean ->
664
+ requireSdk().setGalleryServerEnabled(enabled).values
665
+ }
666
+
632
667
  SdkCoroutineFunction("setGalleryModeEnabled") { enabled: Boolean ->
633
668
  requireSdk().setGalleryModeEnabled(enabled).values
634
669
  }
@@ -391,6 +391,40 @@ public class Bridge private constructor() {
391
391
  sendTypedMessage("switch_status", body)
392
392
  }
393
393
 
394
+ /**
395
+ * Mic tuning values the glasses report as actually in force (post-clamp),
396
+ * from sr_mictun / sr_micst.
397
+ */
398
+ @JvmStatic
399
+ fun sendMicTuningState(state: Map<String, Any>) {
400
+ sendTypedMessage("mic_tuning_state", HashMap(state))
401
+ }
402
+
403
+ /** Wear state from sr_wrst, or from a wear transition on the glasses. */
404
+ @JvmStatic
405
+ fun sendWearState(worn: Boolean) {
406
+ val body = HashMap<String, Any>()
407
+ body["worn"] = worn
408
+ sendTypedMessage("wear_state", body)
409
+ }
410
+
411
+ /** Wear vote config the glasses report as in force, from sr_weartun. */
412
+ @JvmStatic
413
+ fun sendWearTuningState(state: Map<String, Any>) {
414
+ sendTypedMessage("wear_tuning", HashMap(state))
415
+ }
416
+
417
+ /** Disposable mic RMS sample from sr_micrms. */
418
+ @JvmStatic
419
+ fun sendMicRms(rms: Int, gateOpen: Boolean, speakerElevated: Boolean, generation: Int) {
420
+ val body = HashMap<String, Any>()
421
+ body["rms"] = rms
422
+ body["gateOpen"] = gateOpen
423
+ body["speakerElevated"] = speakerElevated
424
+ body["generation"] = generation
425
+ sendTypedMessage("mic_rms", body)
426
+ }
427
+
394
428
  @JvmStatic
395
429
  fun sendPhotoError(requestId: String, errorCode: String, errorMessage: String) {
396
430
  val timestamp = System.currentTimeMillis()
@@ -1883,6 +1883,31 @@ class DeviceManager {
1883
1883
  sgc?.dbg2()
1884
1884
  }
1885
1885
 
1886
+ fun queryWearState() {
1887
+ Bridge.log("MAN: queryWearState()")
1888
+ sgc?.queryWearState()
1889
+ }
1890
+
1891
+ fun setWearReporting(enabled: Boolean) {
1892
+ Bridge.log("MAN: setWearReporting($enabled)")
1893
+ sgc?.setWearReporting(enabled)
1894
+ }
1895
+
1896
+ fun setWearTuning(intervalMs: Int, count: Int, majority: Int) {
1897
+ Bridge.log("MAN: setWearTuning($intervalMs, $count, $majority)")
1898
+ sgc?.setWearTuning(intervalMs, count, majority)
1899
+ }
1900
+
1901
+ fun requestWearTuning() {
1902
+ Bridge.log("MAN: requestWearTuning()")
1903
+ sgc?.requestWearTuning()
1904
+ }
1905
+
1906
+ fun resetWearTuning() {
1907
+ Bridge.log("MAN: resetWearTuning()")
1908
+ sgc?.resetWearTuning()
1909
+ }
1910
+
1886
1911
  fun startStream(message: MutableMap<String, Any>) {
1887
1912
  Bridge.log("MAN: startStream")
1888
1913
  sgc?.startStream(message)
@@ -1976,6 +2001,11 @@ class DeviceManager {
1976
2001
  ar99.sendFactoryReset()
1977
2002
  }
1978
2003
 
2004
+ fun sendGalleryServerEnabled(requestId: String, enabled: Boolean) {
2005
+ val live = sgc as? MentraLive ?: throw IllegalStateException("unsupported_device")
2006
+ live.sendGalleryServerEnabled(requestId, enabled)
2007
+ }
2008
+
1979
2009
  fun sendGalleryMode(requestId: String, enabled: Boolean) {
1980
2010
  val live = sgc as? MentraLive ?: throw IllegalStateException("unsupported_device")
1981
2011
  live.sendGalleryMode(requestId, enabled)
@@ -31,6 +31,16 @@ object DeviceStore {
31
31
  JSONObject(mapOf("v" to newValue)).toString()
32
32
  }
33
33
 
34
+ /**
35
+ * Empty/`reset` mic tuning must still be written to the glasses. Native
36
+ * `update` drops null, and equal empty maps would otherwise skip BLE.
37
+ */
38
+ private fun isMicTuningReset(value: Any): Boolean {
39
+ val map = value as? Map<*, *> ?: return true
40
+ if (map.isEmpty()) return true
41
+ return map.entries.none { (key, field) -> key != "reset" && field is Number }
42
+ }
43
+
34
44
  private fun scheduleDashboardHeightToGlasses() {
35
45
  pendingDashboardHeightRunnable?.let { dashboardBleHandler.removeCallbacks(it) }
36
46
  val r = Runnable {
@@ -164,7 +174,10 @@ object DeviceStore {
164
174
  fun apply(category: String, key: String, value: Any) {
165
175
  val oldValue = store.get(category, key)
166
176
  store.set(category, key, value)
167
- if (observableStoreWouldHaveSkipped(oldValue, value)) {
177
+ val skipped = observableStoreWouldHaveSkipped(oldValue, value)
178
+ val forceMicReset =
179
+ category == ObservableStore.BLUETOOTH_CATEGORY && key == "mic_tuning" && isMicTuningReset(value)
180
+ if (skipped && !forceMicReset) {
168
181
  return
169
182
  }
170
183
 
@@ -278,6 +291,12 @@ object DeviceStore {
278
291
  "bluetooth" to "loudness_gate_enabled" -> {
279
292
  DeviceManager.getInstance().sgc?.sendLoudnessGateSetting()
280
293
  }
294
+ // Deliberately has no seeded default above: the key starts absent so
295
+ // that a session where the engine has not authorized tuning can only
296
+ // ever send a reset.
297
+ "bluetooth" to "mic_tuning" -> {
298
+ DeviceManager.getInstance().sgc?.sendMicTuningSetting()
299
+ }
281
300
  "bluetooth" to "nex_lc3_audio_playback" -> {
282
301
  (value as? Boolean)?.let { enabled ->
283
302
  Bridge.log("DeviceStore: nex_lc3_audio_playback changed to $enabled")
@@ -3,7 +3,8 @@ package com.mentra.bluetoothsdk
3
3
  /** Generated from /changelogs. Do not edit directly. */
4
4
  internal val GENERATED_RELEASE_CHANGELOGS: List<ReleaseChangelog> =
5
5
  listOf(
6
+ ReleaseChangelog(version = "3.2.1", markdown = "This release is under active development. User-facing changes will be documented as they land."),
6
7
  ReleaseChangelog(version = "3.2.0", markdown = "This release is under active development. User-facing changes will be documented as they land.\n\n- Breaking Bluetooth SDK change: `forgetWifiNetwork(ssid)` now returns `WifiForgetResult` instead of `WifiStatusChangeEvent`. Migrate status-only consumers to the semantic `outcome`; the requested network remains `ssid`, while optional post-command connectivity is reported by `connected`, `currentSsid`, and `localIp`."),
7
- ReleaseChangelog(version = "3.1.1", markdown = "Software updates are more reliable, with clearer progress and recovery when the glasses restart.\n\n- MentraOS, Mentra Engine, the Bluetooth SDK, and the glasses client now share one coordinated release version.\n- Mentra Live updates can continue across APK, system, and firmware restarts without asking the user to start the same update again.\n- Bluetooth photo capture and transfer diagnostics are more precise and less disruptive to normal glasses traffic.\n- Bluetooth SDK usage analytics now report the host app's version, build type, and install source (store, TestFlight, sideload, simulator), plus the glasses firmware versions on identification, so store usage can be measured separately from development builds.\n- Bluetooth SDK usage analytics now identify G2 glasses by serial, keep long-lived connections visible across day boundaries, and retry failed uploads instead of dropping them."),
8
- ReleaseChangelog(version = "3.1.0", markdown = "Software updates are more reliable, with clearer progress and recovery when the glasses restart.\n\n- MentraOS, Mentra Engine, the Bluetooth SDK, and the glasses client now share one coordinated release version.\n- Mentra Live updates can continue across APK, system, and firmware restarts without asking the user to start the same update again.\n- Bluetooth photo capture and transfer diagnostics are more precise and less disruptive to normal glasses traffic.\n- Bluetooth SDK usage analytics now report the host app's version, build type, and install source (store, TestFlight, sideload, simulator), plus the glasses firmware versions on identification, so store usage can be measured separately from development builds.\n- Bluetooth SDK usage analytics now identify G2 glasses by serial, keep long-lived connections visible across day boundaries, and retry failed uploads instead of dropping them."),
8
+ ReleaseChangelog(version = "3.1.1", markdown = "- Improved Google login on iOS.\n- Pairing no longer requires phone microphone access."),
9
+ ReleaseChangelog(version = "3.1.0", markdown = "**Mentra App and glasses**\n\n- Improved glasses pairing and reconnection, including on iPad.\n- Improved Android background reliability for captions and miniapps.\n- Added Xingyi AR99 support.\n- More reliable photo and video capture, with improved shutter sounds and privacy-light timing. Taking photos no longer interrupts your running miniapp.\n- Added video thumbnails and smoother gallery browsing.\n- Easier Mentra Live updates through your phone, with clearer progress and better recovery from interruptions.\n\n**Miniapp updates**\n\n- **Mentra AI:** Control whether photos are included with questions. Improved visual answers and connected-app follow-ups.\n- **Captions:** Added offline transcription settings and top/bottom positioning on supported displays.\n- **Translation:** Restored all 60 languages.\n- **Mentra Maps:** Clearer directions on G1/G2, less display flickering, and improved Android offline routing.\n- **Livestreamer:** More video-quality controls and custom streaming options, plus improved startup and sharing.\n- **Merge:** Tap to expand an insight; swipe down or back to dismiss it.\n- **Mentra Notes:** Smoother back navigation.\n- **Recorder:** Fixed audio sharing."),
9
10
  )
@@ -2,10 +2,10 @@ package com.mentra.bluetoothsdk
2
2
 
3
3
  /** Generated by release CI. Do not edit in a release checkout. */
4
4
  internal object GeneratedReleaseMetadata {
5
- const val FAMILY_BASE_VERSION: String = "3.2.0"
6
- const val RELEASE_IDENTITY: String = "3.2.0-dev.265"
7
- const val RELEASE_SET_ID: String = "mentra-3.2.0-dev.265"
8
- const val SOURCE_COMMIT: String = "e8e1ced74c05705b313769d49dfbf98f8bf5b1d2"
9
- const val OTA_MANIFEST_URL: String = "https://github.com/Mentra-Community/MentraOS/releases/download/mentra-builds-v3.2.0/mentra-live-ota-3.2.0-dev.265.json"
10
- const val OTA_MANIFEST_SHA256: String = "c75ed12b14a9897db8c89a9bd3baa6bad432aa908e7fc0b0f50af8c7d8b8a9ad"
5
+ const val FAMILY_BASE_VERSION: String = "3.2.1"
6
+ const val RELEASE_IDENTITY: String = "3.2.1-beta.312"
7
+ const val RELEASE_SET_ID: String = "mentra-3.2.1-beta.312"
8
+ const val SOURCE_COMMIT: String = "0e549d30e1de230cc9f22e77b80ca1704833cceb"
9
+ const val OTA_MANIFEST_URL: String = "https://artifactscdn.mentraglass.com/Mentra-Community/MentraOS/releases/mentra-builds-v3.2.1/mentra-live-ota-3.2.1-beta.312.json"
10
+ const val OTA_MANIFEST_SHA256: String = "c7d7975d15ab271035e7e26431be200d91664cc4c85b761e373ed4bc4026a3ac"
11
11
  }
@@ -601,6 +601,18 @@ class MentraBluetoothSdk private constructor(
601
601
  DeviceStore.apply(ObservableStore.BLUETOOTH_CATEGORY, "screen_disabled", disabled)
602
602
  }
603
603
 
604
+ /**
605
+ * Enable or disable persistent, unauthenticated HTTP gallery access on the glasses' Wi-Fi.
606
+ * Defaults off and survives glasses restarts. Only enable on a trusted network.
607
+ * The ack reports saved enabled state and current listening/url separately.
608
+ */
609
+ suspend fun setGalleryServerEnabled(enabled: Boolean): SettingsAckEvent =
610
+ performSettingsCommand(
611
+ setting = "gallery_server",
612
+ updateStore = {},
613
+ send = { requestId -> deviceManager.sendGalleryServerEnabled(requestId, enabled) },
614
+ )
615
+
604
616
  suspend fun setGalleryModeEnabled(enabled: Boolean): SettingsAckEvent =
605
617
  performSettingsCommand(
606
618
  setting = "gallery_mode",
@@ -125,6 +125,13 @@ data class SettingsAckEvent(
125
125
  val hardwareApplied: Boolean get() = boolValue(values, "hardwareApplied", "hardware_applied") ?: false
126
126
  val errorCode: String? get() = stringValue(values, "errorCode")
127
127
  val errorMessage: String? get() = stringValue(values, "errorMessage")
128
+ /** Saved setting value, when included in the acknowledgement. */
129
+ val enabled: Boolean? get() = values["enabled"] as? Boolean
130
+ /** Whether the persistent gallery server is currently available over station Wi-Fi. */
131
+ val listening: Boolean? get() = values["listening"] as? Boolean
132
+ /** Current gallery base URL, available only with a running server and station Wi-Fi. */
133
+ val url: String? get() = stringValue(values, "url")
134
+
128
135
  }
129
136
 
130
137
  data class RgbLedControlResponseEvent(
@@ -703,9 +703,8 @@ class Mach1 : SGCManager() {
703
703
  }
704
704
 
705
705
  fun blankScreen() {
706
- // if (connectedGlasses != null){
707
- // connectedGlasses.clear();
708
- // }
706
+ ultraliteSdk?.screenOff()
707
+ screenIsClear = true
709
708
  }
710
709
 
711
710
  fun destroy() {
@@ -781,13 +780,13 @@ class Mach1 : SGCManager() {
781
780
  }
782
781
 
783
782
  fun changeUltraliteLayout(chosenLayout: Layout) {
783
+ ultraliteSdk!!.screenOn()
784
+
784
785
  //don't update layout if it's already setup
785
786
  if (currentUltraliteLayout != null && currentUltraliteLayout == chosenLayout) {
786
787
  return
787
788
  }
788
789
 
789
- ultraliteSdk!!.screenOn()
790
-
791
790
  currentUltraliteLayout = chosenLayout
792
791
  ultraliteSdk!!.setLayout(chosenLayout, 0, true, false, 2)
793
792