@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.
- package/README.md +75 -19
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +13 -13
- package/android/src/main/java/com/mentra/bluetoothsdk/Bridge.kt +42 -18
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +19 -21
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +4 -4
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothModels.kt +118 -103
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +110 -38
- package/android/src/main/java/com/mentra/bluetoothsdk/controllers/ControllerManager.kt +4 -4
- package/android/src/main/java/com/mentra/bluetoothsdk/controllers/R1.kt +2 -2
- package/android/src/main/java/com/mentra/bluetoothsdk/services/PhoneMic.kt +6 -6
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G1.java +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G2.kt +3 -3
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Mach1.java +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.java +18 -18
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraNex.kt +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/SGCManager.kt +4 -4
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Simulated.kt +2 -2
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/Constants.kt +6 -6
- package/app.plugin.js +3 -1
- package/build/BluetoothSdk.types.d.ts +126 -27
- package/build/BluetoothSdk.types.d.ts.map +1 -1
- package/build/BluetoothSdk.types.js.map +1 -1
- package/build/_internal.d.ts +12 -0
- package/build/_internal.d.ts.map +1 -0
- package/build/_internal.js +11 -0
- package/build/_internal.js.map +1 -0
- package/build/{BluetoothSdkModule.d.ts → _private/BluetoothSdkModule.d.ts} +19 -8
- package/build/_private/BluetoothSdkModule.d.ts.map +1 -0
- package/build/{BluetoothSdkModule.js → _private/BluetoothSdkModule.js} +41 -26
- package/build/_private/BluetoothSdkModule.js.map +1 -0
- package/build/index.d.ts +4 -2
- package/build/index.d.ts.map +1 -1
- package/build/index.js +3 -4
- package/build/index.js.map +1 -1
- package/build/react/index.d.ts +5 -0
- package/build/react/index.d.ts.map +1 -0
- package/build/react/index.js +5 -0
- package/build/react/index.js.map +1 -0
- package/build/react/useBluetoothEvent.d.ts +6 -0
- package/build/react/useBluetoothEvent.d.ts.map +1 -0
- package/build/react/useBluetoothEvent.js +21 -0
- package/build/react/useBluetoothEvent.js.map +1 -0
- package/build/react/useBluetoothScan.d.ts +22 -0
- package/build/react/useBluetoothScan.d.ts.map +1 -0
- package/build/react/useBluetoothScan.js +135 -0
- package/build/react/useBluetoothScan.js.map +1 -0
- package/build/react/useBluetoothStatus.d.ts +16 -0
- package/build/react/useBluetoothStatus.d.ts.map +1 -0
- package/build/react/useBluetoothStatus.js +137 -0
- package/build/react/useBluetoothStatus.js.map +1 -0
- package/build/react/useGlassesConnection.d.ts +29 -0
- package/build/react/useGlassesConnection.d.ts.map +1 -0
- package/build/react/useGlassesConnection.js +140 -0
- package/build/react/useGlassesConnection.js.map +1 -0
- package/ios/BluetoothSdkModule.swift +14 -9
- package/ios/Source/Bridge.swift +44 -12
- package/ios/Source/DeviceManager.swift +27 -26
- package/ios/Source/DeviceStore.swift +4 -4
- package/ios/Source/MentraBluetoothSDK.swift +250 -132
- package/ios/Source/controllers/ControllerManager.swift +5 -5
- package/ios/Source/controllers/R1.swift +3 -3
- package/ios/Source/services/PhoneMic.swift +5 -5
- package/ios/Source/sgcs/Frame.swift +1 -1
- package/ios/Source/sgcs/G1.swift +1 -1
- package/ios/Source/sgcs/G2.swift +3 -3
- package/ios/Source/sgcs/Mach1.swift +1 -1
- package/ios/Source/sgcs/MentraLive.swift +31 -33
- package/ios/Source/sgcs/MentraNex.swift +2 -2
- package/ios/Source/sgcs/SGCManager.swift +5 -5
- package/ios/Source/sgcs/Simulated.swift +3 -3
- package/ios/Source/utils/Constants.swift +6 -6
- package/ios/Source/utils/JSCExperiment.swift +8 -8
- package/ios/Source/utils/TarBz2Extractor.swift +2 -2
- package/package.json +19 -1
- package/src/BluetoothSdk.types.ts +186 -28
- package/src/_internal.ts +11 -0
- package/src/{BluetoothSdkModule.ts → _private/BluetoothSdkModule.ts} +71 -38
- package/src/index.ts +80 -4
- package/src/react/index.ts +22 -0
- package/src/react/useBluetoothEvent.ts +38 -0
- package/src/react/useBluetoothScan.ts +173 -0
- package/src/react/useBluetoothStatus.ts +180 -0
- package/src/react/useGlassesConnection.ts +194 -0
- package/build/BluetoothSdkModule.d.ts.map +0 -1
- 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
|
-
|
|
27
|
-
|
|
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
|
-
|
|
66
|
-
|
|
67
|
+
bunx expo prebuild
|
|
68
|
+
bunx expo run:ios
|
|
67
69
|
# or
|
|
68
|
-
|
|
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
|
-
`
|
|
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.
|
|
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.
|
|
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`,
|
|
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
|
|
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
|
-
|
|
256
|
-
MENTRA_BLUETOOTH_SDK_PACKAGE_PATH=/path/to/MentraOS/mobile/modules/bluetooth-sdk
|
|
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 `
|
|
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
|
|
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
|
-
"
|
|
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("
|
|
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,
|
|
391
|
-
sdk?.startVideoRecording(VideoRecordingRequest(requestId, save,
|
|
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
|
-
|
|
453
|
-
|
|
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
|
-
|
|
462
|
-
|
|
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 ?:
|
|
508
|
-
val name = values["name"] as? String ?:
|
|
509
|
-
val address = values["address"] 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["
|
|
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
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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["
|
|
259
|
-
body["
|
|
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["
|
|
281
|
-
body["
|
|
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["
|
|
409
|
-
galleryData["
|
|
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["
|
|
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", "
|
|
173
|
-
set(value) = DeviceStore.apply("bluetooth", "
|
|
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
|
|
181
|
-
get() = DeviceStore.store.get("glasses", "
|
|
182
|
-
set(value) = DeviceStore.apply("glasses", "
|
|
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.
|
|
731
|
-
micMode == MicTypes.
|
|
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.
|
|
794
|
-
micMode == MicTypes.
|
|
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,
|
|
1356
|
+
fun startVideoRecording(requestId: String, save: Boolean, sound: Boolean) {
|
|
1356
1357
|
Bridge.log(
|
|
1357
|
-
"MAN: onStartVideoRecording: requestId=$requestId, save=$save, flash
|
|
1358
|
+
"MAN: onStartVideoRecording: requestId=$requestId, save=$save, flash=true, sound=$sound"
|
|
1358
1359
|
)
|
|
1359
|
-
sgc?.startVideoRecording(requestId, save,
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1397
|
-
|
|
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,
|
|
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", "
|
|
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", "
|
|
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, "
|
|
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 "
|
|
242
|
+
"bluetooth" to "galleryModeAuto" -> {
|
|
243
243
|
DeviceManager.getInstance().sgc?.sendGalleryMode()
|
|
244
244
|
}
|
|
245
245
|
"bluetooth" to "screen_disabled" -> {
|