@mentra/bluetooth-sdk 0.1.2 → 0.1.4

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 (85) hide show
  1. package/README.md +75 -19
  2. package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +13 -13
  3. package/android/src/main/java/com/mentra/bluetoothsdk/Bridge.kt +42 -18
  4. package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +19 -21
  5. package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +4 -4
  6. package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothModels.kt +118 -103
  7. package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +110 -38
  8. package/android/src/main/java/com/mentra/bluetoothsdk/controllers/ControllerManager.kt +4 -4
  9. package/android/src/main/java/com/mentra/bluetoothsdk/controllers/R1.kt +2 -2
  10. package/android/src/main/java/com/mentra/bluetoothsdk/services/PhoneMic.kt +6 -6
  11. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G1.java +1 -1
  12. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G2.kt +3 -3
  13. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Mach1.java +1 -1
  14. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.java +18 -18
  15. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraNex.kt +1 -1
  16. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/SGCManager.kt +4 -4
  17. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Simulated.kt +2 -2
  18. package/android/src/main/java/com/mentra/bluetoothsdk/utils/Constants.kt +6 -6
  19. package/app.plugin.js +3 -1
  20. package/build/BluetoothSdk.types.d.ts +126 -27
  21. package/build/BluetoothSdk.types.d.ts.map +1 -1
  22. package/build/BluetoothSdk.types.js.map +1 -1
  23. package/build/_internal.d.ts +12 -0
  24. package/build/_internal.d.ts.map +1 -0
  25. package/build/_internal.js +11 -0
  26. package/build/_internal.js.map +1 -0
  27. package/build/{BluetoothSdkModule.d.ts → _private/BluetoothSdkModule.d.ts} +19 -8
  28. package/build/_private/BluetoothSdkModule.d.ts.map +1 -0
  29. package/build/{BluetoothSdkModule.js → _private/BluetoothSdkModule.js} +41 -26
  30. package/build/_private/BluetoothSdkModule.js.map +1 -0
  31. package/build/index.d.ts +4 -2
  32. package/build/index.d.ts.map +1 -1
  33. package/build/index.js +3 -4
  34. package/build/index.js.map +1 -1
  35. package/build/react/index.d.ts +5 -0
  36. package/build/react/index.d.ts.map +1 -0
  37. package/build/react/index.js +5 -0
  38. package/build/react/index.js.map +1 -0
  39. package/build/react/useBluetoothEvent.d.ts +6 -0
  40. package/build/react/useBluetoothEvent.d.ts.map +1 -0
  41. package/build/react/useBluetoothEvent.js +21 -0
  42. package/build/react/useBluetoothEvent.js.map +1 -0
  43. package/build/react/useBluetoothScan.d.ts +22 -0
  44. package/build/react/useBluetoothScan.d.ts.map +1 -0
  45. package/build/react/useBluetoothScan.js +135 -0
  46. package/build/react/useBluetoothScan.js.map +1 -0
  47. package/build/react/useBluetoothStatus.d.ts +16 -0
  48. package/build/react/useBluetoothStatus.d.ts.map +1 -0
  49. package/build/react/useBluetoothStatus.js +137 -0
  50. package/build/react/useBluetoothStatus.js.map +1 -0
  51. package/build/react/useGlassesConnection.d.ts +29 -0
  52. package/build/react/useGlassesConnection.d.ts.map +1 -0
  53. package/build/react/useGlassesConnection.js +140 -0
  54. package/build/react/useGlassesConnection.js.map +1 -0
  55. package/ios/BluetoothSdkModule.swift +14 -9
  56. package/ios/Source/Bridge.swift +44 -12
  57. package/ios/Source/DeviceManager.swift +27 -26
  58. package/ios/Source/DeviceStore.swift +4 -4
  59. package/ios/Source/MentraBluetoothSDK.swift +250 -132
  60. package/ios/Source/controllers/ControllerManager.swift +5 -5
  61. package/ios/Source/controllers/R1.swift +3 -3
  62. package/ios/Source/services/PhoneMic.swift +5 -5
  63. package/ios/Source/sgcs/Frame.swift +1 -1
  64. package/ios/Source/sgcs/G1.swift +1 -1
  65. package/ios/Source/sgcs/G2.swift +3 -3
  66. package/ios/Source/sgcs/Mach1.swift +1 -1
  67. package/ios/Source/sgcs/MentraLive.swift +31 -33
  68. package/ios/Source/sgcs/MentraNex.swift +2 -2
  69. package/ios/Source/sgcs/SGCManager.swift +5 -5
  70. package/ios/Source/sgcs/Simulated.swift +3 -3
  71. package/ios/Source/utils/Constants.swift +6 -6
  72. package/ios/Source/utils/JSCExperiment.swift +8 -8
  73. package/ios/Source/utils/TarBz2Extractor.swift +2 -2
  74. package/package.json +19 -1
  75. package/src/BluetoothSdk.types.ts +186 -28
  76. package/src/_internal.ts +11 -0
  77. package/src/{BluetoothSdkModule.ts → _private/BluetoothSdkModule.ts} +71 -38
  78. package/src/index.ts +80 -4
  79. package/src/react/index.ts +22 -0
  80. package/src/react/useBluetoothEvent.ts +38 -0
  81. package/src/react/useBluetoothScan.ts +173 -0
  82. package/src/react/useBluetoothStatus.ts +180 -0
  83. package/src/react/useGlassesConnection.ts +194 -0
  84. package/build/BluetoothSdkModule.d.ts.map +0 -1
  85. package/build/BluetoothSdkModule.js.map +0 -1
package/README.md CHANGED
@@ -5,6 +5,8 @@ React Native and Expo SDK for connecting mobile apps directly to supported Mentr
5
5
  The package includes:
6
6
 
7
7
  - A React Native / Expo module API exposed as `BluetoothSdk`.
8
+ - React hooks under `@mentra/bluetooth-sdk/react` for common scan,
9
+ connection, status, and event lifecycles.
8
10
  - Native Android code published as `com.mentra:bluetooth-sdk`.
9
11
  - Native iOS code published as the `MentraBluetoothSDK` CocoaPod.
10
12
  - An Expo config plugin that wires the native dependencies into generated Android and iOS projects.
@@ -23,8 +25,8 @@ Use a development build or production native build. Expo Go cannot load this pac
23
25
  ## Install
24
26
 
25
27
  ```sh
26
- npm install @mentra/bluetooth-sdk
27
- npx expo install expo-build-properties
28
+ bun add @mentra/bluetooth-sdk
29
+ bunx expo install expo-build-properties
28
30
  ```
29
31
 
30
32
  For Expo apps, add the plugin to `app.json` or `app.config.ts`:
@@ -62,10 +64,10 @@ For Expo apps, add the plugin to `app.json` or `app.config.ts`:
62
64
  Then regenerate native projects and run a development build:
63
65
 
64
66
  ```sh
65
- npx expo prebuild
66
- npx expo run:ios
67
+ bunx expo prebuild
68
+ bunx expo run:ios
67
69
  # or
68
- npx expo run:android
70
+ bunx expo run:android
69
71
  ```
70
72
 
71
73
  ## Permissions
@@ -107,7 +109,7 @@ iOS apps should include usage descriptions:
107
109
 
108
110
  ## Minimal Usage
109
111
 
110
- `connectFirst()` scans for the first matching glasses and connects to them. It times out after 15 seconds by default. Use `startScan()` plus `onBluetoothStatus()` only when your app needs to present a custom device picker.
112
+ Use `scan()` when your app needs to show a picker. It calls `onResults` every time the discovered list changes, then resolves with the final list after the timeout.
111
113
 
112
114
  ```ts
113
115
  import BluetoothSdk, {
@@ -119,7 +121,19 @@ const removeGlassesListener = BluetoothSdk.onGlassesStatus((status) => {
119
121
  console.log('Glasses status changed', status)
120
122
  })
121
123
 
122
- await BluetoothSdk.connectFirst(DeviceModels.MentraLive)
124
+ const devices = await BluetoothSdk.scan(DeviceModels.MentraLive, {
125
+ timeoutMs: 10_000,
126
+ onResults: (nextDevices) => {
127
+ console.log('Nearby glasses:', nextDevices)
128
+ },
129
+ })
130
+
131
+ const device = devices[0]
132
+ if (!device) {
133
+ throw new Error('No Mentra Live glasses found')
134
+ }
135
+
136
+ await BluetoothSdk.connect(device)
123
137
 
124
138
  const glasses = await BluetoothSdk.getGlassesStatus()
125
139
  if (isReadyGlassesConnectionStatus(glasses.connection)) {
@@ -129,6 +143,46 @@ if (isReadyGlassesConnectionStatus(glasses.connection)) {
129
143
  removeGlassesListener()
130
144
  ```
131
145
 
146
+ ## React Hooks
147
+
148
+ React Native apps can import optional lifecycle helpers from the `react`
149
+ subpath. The hooks use the same SDK types and still leave commands such as
150
+ `requestPhoto()`, `startStream()`, and `setMicState()` on the root
151
+ `BluetoothSdk` object.
152
+
153
+ ```tsx
154
+ import {Button, Text, View} from 'react-native'
155
+ import {DeviceModels} from '@mentra/bluetooth-sdk'
156
+ import {useBluetoothEvent, useGlassesConnection} from '@mentra/bluetooth-sdk/react'
157
+
158
+ export function DeviceScreen() {
159
+ const glasses = useGlassesConnection({
160
+ scanModel: DeviceModels.MentraLive,
161
+ scanTimeoutMs: 10_000,
162
+ })
163
+
164
+ useBluetoothEvent('button_press', (event) => {
165
+ console.log('Glasses button:', event.buttonId, event.pressType)
166
+ })
167
+
168
+ return (
169
+ <View>
170
+ <Text>{glasses.connected ? 'Connected' : 'Disconnected'}</Text>
171
+ <Button disabled={glasses.busy} title="Scan" onPress={() => glasses.scan.startScan()} />
172
+ {glasses.scan.devices.map((device) => (
173
+ <Button key={device.id} title={device.name} onPress={() => glasses.connect(device)} />
174
+ ))}
175
+ <Button disabled={!glasses.connected} title="Disconnect" onPress={glasses.disconnect} />
176
+ </View>
177
+ )
178
+ }
179
+ ```
180
+
181
+ The hooks do not request Android permissions or choose a persistence package for
182
+ you. Ask for permissions in your app before calling scan/connect actions, and
183
+ pass a `defaultDeviceStorage` adapter to `useGlassesConnection` if you want a
184
+ default device to survive app restarts.
185
+
132
186
  React Native status exposes `GlassesStatus.connection` as a discriminated union:
133
187
 
134
188
  ```ts
@@ -166,7 +220,6 @@ await saveDeviceToYourAppStorage(null)
166
220
  ```ts
167
221
  await BluetoothSdk.clearDisplay()
168
222
  await BluetoothSdk.showDashboard()
169
- await BluetoothSdk.setBrightness(60, false)
170
223
  await BluetoothSdk.setDashboardPosition(4, 2)
171
224
 
172
225
  await BluetoothSdk.requestWifiScan()
@@ -195,19 +248,18 @@ await BluetoothSdk.rgbLedControl(
195
248
  ## Photo Upload
196
249
 
197
250
  ```ts
198
- await BluetoothSdk.photoRequest(
251
+ await BluetoothSdk.requestPhoto(
199
252
  `photo-${Date.now()}`,
200
253
  'com.example.app',
201
254
  'medium',
202
255
  'https://api.example.com/mentra/photo',
203
256
  'optional-token',
204
257
  'medium',
205
- false,
206
258
  true,
207
259
  )
208
260
  ```
209
261
 
210
- The webhook should accept multipart form data with a `photo` file and `requestId`. If `authToken` is provided, the uploader adds `Authorization: Bearer <token>`.
262
+ The webhook should accept multipart form data with a `photo` file and `requestId`. If `authToken` is provided, the uploader adds `Authorization: Bearer <token>`. The camera light is always enabled for photo capture.
211
263
 
212
264
  ## Streaming
213
265
 
@@ -229,7 +281,7 @@ await BluetoothSdk.keepStreamAlive({
229
281
  await BluetoothSdk.stopStream()
230
282
  ```
231
283
 
232
- Use `rtmp://` or `rtmps://` for RTMP, `srt://` for SRT, and `http://` or `https://` for WHIP/WebRTC ingest. Send keep-alives about every 15 seconds while streaming.
284
+ Use `rtmp://` or `rtmps://` for RTMP, `srt://` for SRT, and `http://` or `https://` for WHIP/WebRTC ingest. Send keep-alives about every 15 seconds while streaming. The camera light is always enabled while streaming.
233
285
 
234
286
  ## Events
235
287
 
@@ -245,19 +297,23 @@ const subscriptions = [
245
297
  subscriptions.forEach((subscription) => subscription.remove())
246
298
  ```
247
299
 
248
- Common event names include `button_press`, `touch_event`, `head_up`, `battery_status`, `wifi_status_change`, `hotspot_status_change`, `photo_response`, `gallery_status`, `stream_status`, `keep_alive_ack`, `mic_pcm`, `mic_lc3`, `local_transcription`, `rgb_led_control_response`, `audio_connected`, `audio_disconnected`, `log`, `send_command_to_ble`, and `receive_command_from_ble`.
300
+ Common event names include `button_press`, `touch_event`, `head_up`, `battery_status`, `wifi_status_change`, `hotspot_status_change`, `photo_response`, `gallery_status`, `stream_status`, `keep_alive_ack`, `mic_pcm`, `mic_lc3`, `local_transcription`, `rgb_led_control_response`, `audio_connected`, `audio_disconnected`, and `log`.
301
+
302
+ React Native event payload fields use camelCase. For example, `touch_event` includes `gestureName`, `photo_response` success includes `uploadUrl`, and `gallery_status` includes `hasContent` and `cameraBusy`.
303
+
304
+ Only the documented root import is supported for partner apps. Package subpaths or symbols with a leading underscore are private implementation details and can change without notice.
249
305
 
250
306
  ## Local SDK Development
251
307
 
252
- For normal app development, install the published npm package. For SDK development before a package release, install a local checkout and point Metro/native resolution at the same path:
308
+ For normal app development, install the published JavaScript package. For SDK development before a package release, install a local checkout and point Metro/native resolution at the same path:
253
309
 
254
310
  ```sh
255
- npm install --no-save /path/to/MentraOS/mobile/modules/bluetooth-sdk
256
- MENTRA_BLUETOOTH_SDK_PACKAGE_PATH=/path/to/MentraOS/mobile/modules/bluetooth-sdk npx expo run:ios
311
+ bun add --no-save /path/to/MentraOS/mobile/modules/bluetooth-sdk
312
+ MENTRA_BLUETOOTH_SDK_PACKAGE_PATH=/path/to/MentraOS/mobile/modules/bluetooth-sdk bunx expo run:ios
257
313
  ```
258
314
 
259
- Use `npx expo run:android` for Android. Keep local paths in your shell or CI environment, not in committed app config.
315
+ Use `bunx expo run:android` for Android. Keep local paths in your shell or CI environment, not in committed app config.
260
316
 
261
- ## Example App
317
+ ## Starter Example App
262
318
 
263
- The partner example app lives in `Mentra-Bluetooth-SDK-Partner-Kit/examples/react-native`. It demonstrates scan/connect, display, camera photo upload, RTMP/SRT/WebRTC streaming, Wi-Fi/hotspot, microphone PCM, RGB LED, gallery-button mode, and console event inspection.
319
+ The [Mentra Bluetooth SDK Starter Kit](https://github.com/Mentra-Community/Mentra-Bluetooth-SDK-Starter-Kit) includes starter example apps for Android, iOS, and React Native / Expo. The React Native starter demonstrates scan/connect, display, camera photo upload, RTMP/SRT/WebRTC streaming, Wi-Fi/hotspot, microphone PCM, RGB LED, gallery-button mode, and console event inspection.
@@ -40,7 +40,7 @@ class BluetoothSdkModule : Module() {
40
40
  "compatible_glasses_search_stop",
41
41
  mapOf(
42
42
  "type" to "compatible_glasses_search_stop",
43
- "device_model" to deviceModel,
43
+ "deviceModel" to deviceModel,
44
44
  )
45
45
  )
46
46
  }
@@ -294,6 +294,8 @@ class BluetoothSdkModule : Module() {
294
294
  sdk?.startScan(DeviceModel.fromDeviceType(model))
295
295
  }
296
296
 
297
+ AsyncFunction("stopScan") { sdk?.stopScan() }
298
+
297
299
  AsyncFunction("cancelConnectionAttempt") { sdk?.cancelConnectionAttempt() }
298
300
 
299
301
  AsyncFunction("showDashboard") { sdk?.showDashboard() }
@@ -346,14 +348,13 @@ class BluetoothSdkModule : Module() {
346
348
 
347
349
  AsyncFunction("queryGalleryStatus") { sdk?.queryGalleryStatus() }
348
350
 
349
- AsyncFunction("photoRequest") {
351
+ AsyncFunction("requestPhoto") {
350
352
  requestId: String,
351
353
  appId: String,
352
354
  size: String,
353
355
  webhookUrl: String,
354
356
  authToken: String?,
355
357
  compress: String,
356
- flash: Boolean,
357
358
  sound: Boolean ->
358
359
  sdk?.requestPhoto(
359
360
  PhotoRequest(
@@ -363,7 +364,6 @@ class BluetoothSdkModule : Module() {
363
364
  webhookUrl = webhookUrl,
364
365
  authToken = authToken,
365
366
  compress = PhotoCompression.fromValue(compress),
366
- flash = flash,
367
367
  sound = sound,
368
368
  )
369
369
  )
@@ -387,8 +387,8 @@ class BluetoothSdkModule : Module() {
387
387
 
388
388
  // MARK: - Video Recording Commands
389
389
 
390
- AsyncFunction("startVideoRecording") { requestId: String, save: Boolean, flash: Boolean, sound: Boolean ->
391
- sdk?.startVideoRecording(VideoRecordingRequest(requestId, save, flash, sound))
390
+ AsyncFunction("startVideoRecording") { requestId: String, save: Boolean, sound: Boolean ->
391
+ sdk?.startVideoRecording(VideoRecordingRequest(requestId, save, sound))
392
392
  }
393
393
 
394
394
  AsyncFunction("stopVideoRecording") { requestId: String ->
@@ -449,8 +449,8 @@ class BluetoothSdkModule : Module() {
449
449
  packageName: String?,
450
450
  action: String,
451
451
  color: String?,
452
- ontime: Int,
453
- offtime: Int,
452
+ onDurationMs: Int,
453
+ offDurationMs: Int,
454
454
  count: Int ->
455
455
  sdk?.rgbLedControl(
456
456
  RgbLedRequest(
@@ -458,8 +458,8 @@ class BluetoothSdkModule : Module() {
458
458
  packageName = packageName,
459
459
  action = RgbLedAction.fromValue(action),
460
460
  color = RgbLedColor.fromValue(color),
461
- ontime = ontime,
462
- offtime = offtime,
461
+ onDurationMs = onDurationMs,
462
+ offDurationMs = offDurationMs,
463
463
  count = count,
464
464
  )
465
465
  )
@@ -504,9 +504,9 @@ class BluetoothSdkModule : Module() {
504
504
 
505
505
  private fun Map<String, Any>?.toMentraDevice(): Device? {
506
506
  val values = this ?: return null
507
- val model = values["model"] as? String ?: values["deviceModel"] as? String ?: return null
508
- val name = values["name"] as? String ?: values["deviceName"] as? String ?: return null
509
- val address = values["address"] as? String ?: values["deviceAddress"] as? String
507
+ val model = values["model"] as? String ?: return null
508
+ val name = values["name"] as? String ?: return null
509
+ val address = values["address"] as? String
510
510
  val rssi = (values["rssi"] as? Number)?.toInt()
511
511
  val id = values["id"] as? String
512
512
  return Device(
@@ -121,7 +121,7 @@ public class Bridge private constructor() {
121
121
  @JvmStatic
122
122
  fun sendAudioConnected(deviceName: String) {
123
123
  val data = HashMap<String, Any>()
124
- data["device_name"] = deviceName
124
+ data["deviceName"] = deviceName
125
125
  sendTypedMessage("audio_connected", data as Map<String, Any>)
126
126
  }
127
127
 
@@ -211,19 +211,41 @@ public class Bridge private constructor() {
211
211
  }
212
212
  rssi?.let { put("rssi", it) }
213
213
  }
214
- val allResults = searchResults + newResult
215
- val uniqueResults =
216
- allResults
217
- .asReversed()
218
- .distinctBy {
219
- val model = it["model"] ?: it["deviceModel"] ?: deviceModel
220
- val name = it["name"] ?: it["deviceName"] ?: return@distinctBy null
221
- "$model:$name"
222
- }
223
- .asReversed()
214
+ // Keep the public searchResults array stable as glasses are added or removed.
215
+ // Duplicate discoveries refresh their existing row; only new glasses append.
216
+ val uniqueResults = mergeStableSearchResults(searchResults, newResult, deviceModel)
224
217
  DeviceStore.set("bluetooth", "searchResults", uniqueResults)
225
218
  }
226
219
 
220
+ private fun mergeStableSearchResults(
221
+ currentResults: List<Map<String, Any>>,
222
+ newResult: Map<String, Any>,
223
+ fallbackModel: String
224
+ ): List<Map<String, Any>> {
225
+ val newKey = searchResultKey(newResult, fallbackModel) ?: return currentResults
226
+ val nextResults = currentResults.toMutableList()
227
+ val existingIndex =
228
+ nextResults.indexOfFirst { result ->
229
+ searchResultKey(result, fallbackModel) == newKey
230
+ }
231
+ if (existingIndex >= 0) {
232
+ nextResults[existingIndex] = newResult
233
+ } else {
234
+ nextResults += newResult
235
+ }
236
+ return nextResults
237
+ }
238
+
239
+ private fun searchResultKey(result: Map<String, Any>, fallbackModel: String): String? {
240
+ val id = result["id"] as? String
241
+ if (!id.isNullOrBlank()) {
242
+ return id
243
+ }
244
+ val model = result["model"] as? String ?: fallbackModel
245
+ val name = result["name"] as? String ?: return null
246
+ return "$model:$name"
247
+ }
248
+
227
249
  // MARK: - Hardware Events
228
250
 
229
251
  /** Send button press event to React Native - matches iOS implementation */
@@ -255,8 +277,9 @@ public class Bridge private constructor() {
255
277
  source: Int? = null
256
278
  ) {
257
279
  val body = HashMap<String, Any>()
258
- body["device_model"] = deviceModel
259
- body["gesture_name"] = gestureName
280
+ body["type"] = "touch_event"
281
+ body["deviceModel"] = deviceModel
282
+ body["gestureName"] = gestureName
260
283
  body["timestamp"] = timestamp
261
284
  if (source != null) {
262
285
  body["source"] = source
@@ -277,8 +300,8 @@ public class Bridge private constructor() {
277
300
  @JvmStatic
278
301
  fun sendSwitchStatus(switchType: Int, value: Int, timestamp: Long) {
279
302
  val body = HashMap<String, Any>()
280
- body["switch_type"] = switchType
281
- body["switch_value"] = value
303
+ body["switchType"] = switchType
304
+ body["switchValue"] = value
282
305
  body["timestamp"] = timestamp
283
306
  sendTypedMessage("switch_status", body)
284
307
  }
@@ -301,6 +324,7 @@ public class Bridge private constructor() {
301
324
  if (requestId.isEmpty()) return
302
325
  try {
303
326
  val body = HashMap<String, Any>()
327
+ body["type"] = "rgb_led_control_response"
304
328
  body["requestId"] = requestId
305
329
  body["state"] = if (success) "success" else "error"
306
330
  if (!success) {
@@ -405,8 +429,8 @@ public class Bridge private constructor() {
405
429
  galleryData["photos"] = photoCount
406
430
  galleryData["videos"] = videoCount
407
431
  galleryData["total"] = totalCount
408
- galleryData["total_size"] = totalSize
409
- galleryData["has_content"] = hasContent
432
+ galleryData["totalSize"] = totalSize
433
+ galleryData["hasContent"] = hasContent
410
434
 
411
435
  sendTypedMessage("gallery_status", galleryData as Map<String, Any>)
412
436
  }
@@ -427,7 +451,7 @@ public class Bridge private constructor() {
427
451
  @JvmStatic
428
452
  fun sendHotspotError(errorMessage: String, timestamp: Long) {
429
453
  val eventBody = HashMap<String, Any>()
430
- eventBody["error_message"] = errorMessage
454
+ eventBody["errorMessage"] = errorMessage
431
455
  eventBody["timestamp"] = timestamp
432
456
 
433
457
  sendTypedMessage("hotspot_error", eventBody as Map<String, Any>)
@@ -169,17 +169,17 @@ class DeviceManager {
169
169
  set(value) = DeviceStore.apply("bluetooth", "dashboard_depth", value)
170
170
 
171
171
  private var galleryMode: Boolean
172
- get() = DeviceStore.store.get("bluetooth", "gallery_mode") as? Boolean ?: true
173
- set(value) = DeviceStore.apply("bluetooth", "gallery_mode", value)
172
+ get() = DeviceStore.store.get("bluetooth", "galleryModeAuto") as? Boolean ?: true
173
+ set(value) = DeviceStore.apply("bluetooth", "galleryModeAuto", value)
174
174
 
175
175
  // state:
176
176
  private var searching: Boolean
177
177
  get() = DeviceStore.store.get("bluetooth", "searching") as? Boolean ?: false
178
178
  set(value) = DeviceStore.apply("bluetooth", "searching", value)
179
179
 
180
- private var glassesBtcConnected: Boolean
181
- get() = DeviceStore.store.get("glasses", "btcConnected") as? Boolean ?: false
182
- set(value) = DeviceStore.apply("glasses", "btcConnected", value)
180
+ private var glassesBluetoothClassicConnected: Boolean
181
+ get() = DeviceStore.store.get("glasses", "bluetoothClassicConnected") as? Boolean ?: false
182
+ set(value) = DeviceStore.apply("glasses", "bluetoothClassicConnected", value)
183
183
 
184
184
  public var micRanking: MutableList<String>
185
185
  get() =
@@ -727,8 +727,8 @@ class DeviceManager {
727
727
 
728
728
  for (micMode in micRanking) {
729
729
  if (micMode == MicTypes.PHONE_INTERNAL ||
730
- micMode == MicTypes.BT_CLASSIC ||
731
- micMode == MicTypes.BT
730
+ micMode == MicTypes.BLUETOOTH_CLASSIC ||
731
+ micMode == MicTypes.BLUETOOTH
732
732
  ) {
733
733
 
734
734
  if (phoneMic?.isRecordingWithMode(micMode) == true) {
@@ -790,8 +790,8 @@ class DeviceManager {
790
790
  }
791
791
 
792
792
  if (micMode == MicTypes.PHONE_INTERNAL ||
793
- micMode == MicTypes.BT_CLASSIC ||
794
- micMode == MicTypes.BT
793
+ micMode == MicTypes.BLUETOOTH_CLASSIC ||
794
+ micMode == MicTypes.BLUETOOTH
795
795
  ) {
796
796
  phoneMic?.stopMode(micMode)
797
797
  }
@@ -1221,6 +1221,7 @@ class DeviceManager {
1221
1221
 
1222
1222
  fun startStream(message: MutableMap<String, Any>) {
1223
1223
  Bridge.log("MAN: startStream")
1224
+ message["flash"] = true
1224
1225
  sgc?.startStream(message)
1225
1226
  }
1226
1227
 
@@ -1352,11 +1353,11 @@ class DeviceManager {
1352
1353
  sgc?.sendReboot()
1353
1354
  }
1354
1355
 
1355
- fun startVideoRecording(requestId: String, save: Boolean, flash: Boolean, sound: Boolean) {
1356
+ fun startVideoRecording(requestId: String, save: Boolean, sound: Boolean) {
1356
1357
  Bridge.log(
1357
- "MAN: onStartVideoRecording: requestId=$requestId, save=$save, flash=$flash, sound=$sound"
1358
+ "MAN: onStartVideoRecording: requestId=$requestId, save=$save, flash=true, sound=$sound"
1358
1359
  )
1359
- sgc?.startVideoRecording(requestId, save, flash, sound)
1360
+ sgc?.startVideoRecording(requestId, save, true, sound)
1360
1361
  }
1361
1362
 
1362
1363
  fun stopVideoRecording(requestId: String) {
@@ -1372,20 +1373,17 @@ class DeviceManager {
1372
1373
  updateMicState()
1373
1374
  }
1374
1375
 
1375
- fun photoRequest(
1376
+ fun requestPhoto(
1376
1377
  requestId: String,
1377
1378
  appId: String,
1378
1379
  size: String,
1379
1380
  webhookUrl: String,
1380
1381
  authToken: String?,
1381
1382
  compress: String,
1382
- flash: Boolean,
1383
1383
  sound: Boolean
1384
1384
  ) {
1385
- Bridge.log(
1386
- "MAN: onPhotoRequest: $requestId, $appId, $size, compress=$compress, flash=$flash, sound=$sound"
1387
- )
1388
- sgc?.requestPhoto(requestId, appId, size, webhookUrl, authToken, compress, flash, sound)
1385
+ Bridge.log("MAN: requestPhoto: $requestId, $appId, $size, compress=$compress, flash=true, sound=$sound")
1386
+ sgc?.requestPhoto(requestId, appId, size, webhookUrl, authToken, compress, true, sound)
1389
1387
  }
1390
1388
 
1391
1389
  fun rgbLedControl(
@@ -1393,12 +1391,12 @@ class DeviceManager {
1393
1391
  packageName: String?,
1394
1392
  action: String,
1395
1393
  color: String?,
1396
- ontime: Int,
1397
- offtime: Int,
1394
+ onDurationMs: Int,
1395
+ offDurationMs: Int,
1398
1396
  count: Int
1399
1397
  ) {
1400
1398
  Bridge.log("MAN: RGB LED control: action=$action, color=$color, requestId=$requestId")
1401
- sgc?.sendRgbLedControl(requestId, packageName, action, color, ontime, offtime, count)
1399
+ sgc?.sendRgbLedControl(requestId, packageName, action, color, onDurationMs, offDurationMs, count)
1402
1400
  }
1403
1401
 
1404
1402
  fun connectDefault() {
@@ -66,7 +66,7 @@ object DeviceStore {
66
66
  store.set("glasses", "deviceModel", "")
67
67
  store.set("glasses", "firmwareVersion", "")
68
68
  store.set("glasses", "micEnabled", false)
69
- store.set("glasses", "btcConnected", false)
69
+ store.set("glasses", "bluetoothClassicConnected", false)
70
70
  store.set("glasses", "caseRemoved", true)
71
71
  store.set("glasses", "caseOpen", true)
72
72
  store.set("glasses", "caseCharging", false)
@@ -121,12 +121,12 @@ object DeviceStore {
121
121
  store.set("bluetooth", "dashboard_depth", 2)
122
122
  store.set("bluetooth", "head_up_angle", 30)
123
123
  store.set("bluetooth", "contextual_dashboard", true)
124
- store.set("bluetooth", "gallery_mode", true)
124
+ store.set("bluetooth", "galleryModeAuto", true)
125
125
  store.set("bluetooth", "screen_disabled", false)
126
126
  store.set("bluetooth", "button_photo_size", "medium")
127
127
  store.set("bluetooth", "button_camera_led", true)
128
128
  store.set("bluetooth", "button_max_recording_time", 10)
129
- store.set("bluetooth", "camera_fov", mapOf("fov" to 118, "roi_position" to 0))
129
+ store.set("bluetooth", "camera_fov", mapOf("fov" to 118, "roiPosition" to 0))
130
130
  store.set("bluetooth", "button_video_width", 1280)
131
131
  store.set("bluetooth", "button_video_height", 720)
132
132
  store.set("bluetooth", "button_video_fps", 30)
@@ -239,7 +239,7 @@ object DeviceStore {
239
239
  DeviceManager.getInstance().sgc?.setDashboardMenu(items)
240
240
  }
241
241
  }
242
- "bluetooth" to "gallery_mode" -> {
242
+ "bluetooth" to "galleryModeAuto" -> {
243
243
  DeviceManager.getInstance().sgc?.sendGalleryMode()
244
244
  }
245
245
  "bluetooth" to "screen_disabled" -> {