@mentra/bluetooth-sdk 0.1.4 → 0.1.6

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 (72) hide show
  1. package/README.md +61 -42
  2. package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +20 -12
  3. package/android/src/main/java/com/mentra/bluetoothsdk/Bridge.kt +38 -12
  4. package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +2 -1
  5. package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +45 -51
  6. package/android/src/main/java/com/mentra/bluetoothsdk/audio/AudioModels.kt +125 -0
  7. package/android/src/main/java/com/mentra/bluetoothsdk/camera/CameraModels.kt +187 -0
  8. package/android/src/main/java/com/mentra/bluetoothsdk/connection/ConnectionModels.kt +78 -0
  9. package/android/src/main/java/com/mentra/bluetoothsdk/events/BluetoothEvents.kt +60 -0
  10. package/android/src/main/java/com/mentra/bluetoothsdk/internal/MapParsing.kt +132 -0
  11. package/android/src/main/java/com/mentra/bluetoothsdk/requests/DisplayRequests.kt +39 -0
  12. package/android/src/main/java/com/mentra/bluetoothsdk/status/DeviceStatus.kt +605 -0
  13. package/android/src/main/java/com/mentra/bluetoothsdk/status/RuntimeState.kt +199 -0
  14. package/android/src/main/java/com/mentra/bluetoothsdk/status/WifiHotspotStatus.kt +173 -0
  15. package/android/src/main/java/com/mentra/bluetoothsdk/streaming/StreamModels.kt +348 -0
  16. package/android/src/main/java/com/mentra/bluetoothsdk/types/DeviceModels.kt +75 -0
  17. package/build/BluetoothSdk.types.d.ts +24 -7
  18. package/build/BluetoothSdk.types.d.ts.map +1 -1
  19. package/build/BluetoothSdk.types.js.map +1 -1
  20. package/build/_internal.d.ts +1 -1
  21. package/build/_internal.js +1 -1
  22. package/build/_internal.js.map +1 -1
  23. package/build/_private/BluetoothSdkModule.d.ts +5 -4
  24. package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
  25. package/build/_private/BluetoothSdkModule.js +4 -1
  26. package/build/_private/BluetoothSdkModule.js.map +1 -1
  27. package/build/index.d.ts +4 -3
  28. package/build/index.d.ts.map +1 -1
  29. package/build/index.js +81 -2
  30. package/build/index.js.map +1 -1
  31. package/build/react/index.d.ts +2 -2
  32. package/build/react/index.d.ts.map +1 -1
  33. package/build/react/index.js +1 -2
  34. package/build/react/index.js.map +1 -1
  35. package/build/react/useBluetoothStatus.js +1 -1
  36. package/build/react/useBluetoothStatus.js.map +1 -1
  37. package/build/react/useGlassesConnection.d.ts.map +1 -1
  38. package/build/react/useGlassesConnection.js +13 -0
  39. package/build/react/useGlassesConnection.js.map +1 -1
  40. package/build/react/useMentraBluetooth.d.ts +98 -0
  41. package/build/react/useMentraBluetooth.d.ts.map +1 -0
  42. package/build/react/useMentraBluetooth.js +156 -0
  43. package/build/react/useMentraBluetooth.js.map +1 -0
  44. package/ios/BluetoothSdkModule.swift +9 -9
  45. package/ios/Source/Audio/AudioModels.swift +159 -0
  46. package/ios/Source/Bridge.swift +42 -8
  47. package/ios/Source/Camera/CameraModels.swift +246 -0
  48. package/ios/Source/Connection/ScanSession.swift +27 -0
  49. package/ios/Source/DeviceManager.swift +0 -1
  50. package/ios/Source/DeviceStore.swift +2 -1
  51. package/ios/Source/Errors/BluetoothError.swift +19 -0
  52. package/ios/Source/Events/BluetoothEvents.swift +115 -0
  53. package/ios/Source/Internal/BluetoothAvailability.swift +58 -0
  54. package/ios/Source/Internal/ValueParsing.swift +90 -0
  55. package/ios/Source/MentraBluetoothSDK.swift +32 -2040
  56. package/ios/Source/Requests/DisplayRequests.swift +58 -0
  57. package/ios/Source/Status/DeviceStatus.swift +463 -0
  58. package/ios/Source/Status/RuntimeState.swift +350 -0
  59. package/ios/Source/Status/WifiHotspotStatus.swift +326 -0
  60. package/ios/Source/Streaming/StreamModels.swift +436 -0
  61. package/ios/Source/Types/DeviceModels.swift +134 -0
  62. package/ios/Source/sgcs/G2.swift +97 -36
  63. package/package.json +1 -1
  64. package/src/BluetoothSdk.types.ts +24 -7
  65. package/src/_internal.ts +1 -1
  66. package/src/_private/BluetoothSdkModule.ts +10 -5
  67. package/src/index.ts +93 -4
  68. package/src/react/index.ts +13 -11
  69. package/src/react/useBluetoothStatus.ts +1 -1
  70. package/src/react/useGlassesConnection.ts +15 -0
  71. package/src/react/useMentraBluetooth.ts +307 -0
  72. package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothModels.kt +0 -1725
@@ -1,1725 +0,0 @@
1
- package com.mentra.bluetoothsdk
2
-
3
- import com.mentra.bluetoothsdk.utils.ControllerTypes
4
- import com.mentra.bluetoothsdk.utils.ConnTypes
5
- import com.mentra.bluetoothsdk.utils.DeviceTypes
6
-
7
- data class MentraBluetoothSdkConfig(
8
- val deliverCallbacksOnMainThread: Boolean = true,
9
- )
10
-
11
- class BluetoothException(
12
- val code: String,
13
- message: String,
14
- cause: Throwable? = null,
15
- ) : IllegalStateException(message, cause)
16
-
17
- enum class DeviceModel(val deviceType: String) {
18
- G1(DeviceTypes.G1),
19
- G2(DeviceTypes.G2),
20
- MENTRA_LIVE(DeviceTypes.LIVE),
21
- MENTRA_NEX(DeviceTypes.NEX),
22
- MACH1(DeviceTypes.MACH1),
23
- Z100(DeviceTypes.Z100),
24
- FRAME(DeviceTypes.FRAME),
25
- SIMULATED(DeviceTypes.SIMULATED),
26
- R1(ControllerTypes.R1);
27
-
28
- companion object {
29
- @JvmStatic
30
- fun fromDeviceType(deviceType: String?): DeviceModel =
31
- values().firstOrNull { it.deviceType == deviceType } ?: MENTRA_LIVE
32
- }
33
- }
34
-
35
- data class Device(
36
- val model: DeviceModel,
37
- val name: String,
38
- val address: String? = null,
39
- val rssi: Int? = null,
40
- val id: String = address?.takeIf { it.isNotBlank() } ?: "${model.deviceType}:$name",
41
- ) {
42
- internal fun toMap(): Map<String, Any> =
43
- buildMap {
44
- put("id", id)
45
- put("model", model.deviceType)
46
- put("name", name)
47
- address?.takeIf { it.isNotBlank() }?.let {
48
- put("address", it)
49
- }
50
- rssi?.let { put("rssi", it) }
51
- }
52
-
53
- companion object {
54
- internal fun fromMap(values: Map<String, Any>): Device? {
55
- val model = stringValue(values, "model") ?: return null
56
- val name = stringValue(values, "name") ?: return null
57
- val address = stringValue(values, "address")?.takeIf { it.isNotBlank() }
58
- val rssi = numberValue(values, "rssi")
59
- val id = stringValue(values, "id")?.takeIf { it.isNotBlank() } ?: address ?: "${model}:$name"
60
- return Device(
61
- model = DeviceModel.fromDeviceType(model),
62
- name = name,
63
- address = address,
64
- rssi = rssi,
65
- id = id,
66
- )
67
- }
68
- }
69
- }
70
-
71
- data class ConnectOptions(
72
- val saveAsDefault: Boolean = true,
73
- val cancelExistingConnectionAttempt: Boolean = true,
74
- )
75
-
76
- data class WifiScanResult(
77
- val ssid: String,
78
- val requiresPassword: Boolean,
79
- val signalStrength: Int,
80
- val frequency: Int? = null,
81
- ) {
82
- internal fun toMap(): Map<String, Any> {
83
- val map =
84
- mutableMapOf<String, Any>(
85
- "ssid" to ssid,
86
- "requiresPassword" to requiresPassword,
87
- "signalStrength" to signalStrength,
88
- )
89
- frequency?.let { map["frequency"] = it }
90
- return map
91
- }
92
-
93
- companion object {
94
- internal fun fromMap(values: Map<String, Any>): WifiScanResult =
95
- WifiScanResult(
96
- ssid = stringValue(values, "ssid") ?: "",
97
- requiresPassword = boolValue(values, "requiresPassword") ?: false,
98
- signalStrength = numberValue(values, "signalStrength") ?: -1,
99
- frequency = numberValue(values, "frequency"),
100
- )
101
- }
102
- }
103
-
104
- data class GlassesStatus(
105
- val fullyBooted: Boolean,
106
- val connected: Boolean,
107
- val micEnabled: Boolean,
108
- val connectionState: GlassesConnectionState,
109
- val bluetoothClassicConnected: Boolean,
110
- val signalStrength: Int,
111
- val signalStrengthUpdatedAt: Long,
112
- val deviceModel: String,
113
- val androidVersion: String,
114
- val firmwareVersion: String,
115
- val besFirmwareVersion: String,
116
- val mtkFirmwareVersion: String,
117
- val bluetoothMacAddress: String,
118
- val leftMacAddress: String,
119
- val rightMacAddress: String,
120
- val macAddress: String,
121
- val buildNumber: String,
122
- val otaVersionUrl: String,
123
- val appVersion: String,
124
- val bluetoothName: String,
125
- val serialNumber: String,
126
- val style: String,
127
- val color: String,
128
- val wifi: WifiStatus,
129
- val batteryLevel: Int,
130
- val charging: Boolean,
131
- val caseBatteryLevel: Int,
132
- val caseCharging: Boolean,
133
- val caseOpen: Boolean,
134
- val caseRemoved: Boolean,
135
- val hotspot: HotspotStatus,
136
- val headUp: Boolean,
137
- val controllerConnected: Boolean,
138
- val controllerFullyBooted: Boolean,
139
- val controllerMacAddress: String,
140
- val controllerBatteryLevel: Int,
141
- val controllerSignalStrength: Int,
142
- val ringSignalStrength: Int,
143
- ) {
144
- internal fun toMap(): Map<String, Any> =
145
- mapOf(
146
- "connection" to connectionState.toStatusMap(connected, fullyBooted),
147
- "micEnabled" to micEnabled,
148
- "bluetoothClassicConnected" to bluetoothClassicConnected,
149
- "signalStrength" to signalStrength,
150
- "signalStrengthUpdatedAt" to signalStrengthUpdatedAt,
151
- "deviceModel" to deviceModel,
152
- "androidVersion" to androidVersion,
153
- "firmwareVersion" to firmwareVersion,
154
- "besFirmwareVersion" to besFirmwareVersion,
155
- "mtkFirmwareVersion" to mtkFirmwareVersion,
156
- "bluetoothMacAddress" to bluetoothMacAddress,
157
- "leftMacAddress" to leftMacAddress,
158
- "rightMacAddress" to rightMacAddress,
159
- "macAddress" to macAddress,
160
- "buildNumber" to buildNumber,
161
- "otaVersionUrl" to otaVersionUrl,
162
- "appVersion" to appVersion,
163
- "bluetoothName" to bluetoothName,
164
- "serialNumber" to serialNumber,
165
- "style" to style,
166
- "color" to color,
167
- "wifi" to wifi.toMap(),
168
- "batteryLevel" to batteryLevel,
169
- "charging" to charging,
170
- "caseBatteryLevel" to caseBatteryLevel,
171
- "caseCharging" to caseCharging,
172
- "caseOpen" to caseOpen,
173
- "caseRemoved" to caseRemoved,
174
- "hotspot" to hotspot.toMap(),
175
- "headUp" to headUp,
176
- "controllerConnected" to controllerConnected,
177
- "controllerFullyBooted" to controllerFullyBooted,
178
- "controllerMacAddress" to controllerMacAddress,
179
- "controllerBatteryLevel" to controllerBatteryLevel,
180
- "controllerSignalStrength" to controllerSignalStrength,
181
- "ringSignalStrength" to ringSignalStrength,
182
- )
183
-
184
- companion object {
185
- internal fun fromMap(values: Map<String, Any>): GlassesStatus =
186
- GlassesStatus(
187
- fullyBooted = boolValue(values, "fullyBooted") ?: false,
188
- connected = boolValue(values, "connected") ?: false,
189
- micEnabled = boolValue(values, "micEnabled") ?: false,
190
- connectionState = GlassesConnectionState.fromValue(stringValue(values, "connectionState")),
191
- bluetoothClassicConnected = boolValue(values, "bluetoothClassicConnected") ?: false,
192
- signalStrength = numberValue(values, "signalStrength") ?: -1,
193
- signalStrengthUpdatedAt = longValue(values, "signalStrengthUpdatedAt") ?: 0L,
194
- deviceModel = stringValue(values, "deviceModel") ?: "",
195
- androidVersion = stringValue(values, "androidVersion") ?: "",
196
- firmwareVersion = stringValue(values, "firmwareVersion") ?: "",
197
- besFirmwareVersion = stringValue(values, "besFirmwareVersion") ?: "",
198
- mtkFirmwareVersion = stringValue(values, "mtkFirmwareVersion") ?: "",
199
- bluetoothMacAddress = stringValue(values, "bluetoothMacAddress") ?: "",
200
- leftMacAddress = stringValue(values, "leftMacAddress") ?: "",
201
- rightMacAddress = stringValue(values, "rightMacAddress") ?: "",
202
- macAddress = stringValue(values, "macAddress") ?: "",
203
- buildNumber = stringValue(values, "buildNumber") ?: "",
204
- otaVersionUrl = stringValue(values, "otaVersionUrl") ?: "",
205
- appVersion = stringValue(values, "appVersion") ?: "",
206
- bluetoothName = stringValue(values, "bluetoothName") ?: "",
207
- serialNumber = stringValue(values, "serialNumber") ?: "",
208
- style = stringValue(values, "style") ?: "",
209
- color = stringValue(values, "color") ?: "",
210
- wifi = WifiStatus.fromStoreMap(values) ?: WifiStatus.Disconnected,
211
- batteryLevel = numberValue(values, "batteryLevel") ?: -1,
212
- charging = boolValue(values, "charging") ?: false,
213
- caseBatteryLevel = numberValue(values, "caseBatteryLevel") ?: -1,
214
- caseCharging = boolValue(values, "caseCharging") ?: false,
215
- caseOpen = boolValue(values, "caseOpen") ?: true,
216
- caseRemoved = boolValue(values, "caseRemoved") ?: true,
217
- hotspot = HotspotStatus.fromStoreMap(values) ?: HotspotStatus.Disabled,
218
- headUp = boolValue(values, "headUp") ?: false,
219
- controllerConnected = boolValue(values, "controllerConnected") ?: false,
220
- controllerFullyBooted = boolValue(values, "controllerFullyBooted") ?: false,
221
- controllerMacAddress = stringValue(values, "controllerMacAddress") ?: "",
222
- controllerBatteryLevel = numberValue(values, "controllerBatteryLevel") ?: -1,
223
- controllerSignalStrength = numberValue(values, "controllerSignalStrength") ?: -1,
224
- ringSignalStrength = numberValue(values, "ringSignalStrength") ?: -1,
225
- )
226
- }
227
- }
228
-
229
- data class BluetoothStatus(
230
- val searching: Boolean,
231
- val searchingController: Boolean,
232
- val systemMicUnavailable: Boolean,
233
- val micEnabled: Boolean,
234
- val currentMic: String,
235
- val micRanking: List<String>,
236
- /**
237
- * Nearby glasses in stable discovery order. Existing entries keep their array position as
238
- * details refresh; new glasses append at the end, and removals should not reorder remaining
239
- * entries.
240
- */
241
- val searchResults: List<Device>,
242
- val wifiScanResults: List<WifiScanResult>,
243
- val lastLog: List<String>,
244
- val otherBtConnected: Boolean,
245
- val defaultWearable: String,
246
- val pendingWearable: String,
247
- val deviceName: String,
248
- val deviceAddress: String,
249
- val defaultController: String,
250
- val pendingController: String,
251
- val controllerDeviceName: String,
252
- val screenDisabled: Boolean,
253
- val preferredMic: String,
254
- val sensingEnabled: Boolean,
255
- val powerSavingMode: Boolean,
256
- val brightness: Int,
257
- val autoBrightness: Boolean,
258
- val dashboardHeight: Int,
259
- val dashboardDepth: Int,
260
- val headUpAngle: Int,
261
- val contextualDashboard: Boolean,
262
- val galleryModeAuto: Boolean,
263
- val buttonPhotoSize: ButtonPhotoSize,
264
- val buttonCameraLed: Boolean,
265
- val buttonMaxRecordingTime: Int,
266
- val buttonVideoWidth: Int,
267
- val buttonVideoHeight: Int,
268
- val buttonVideoFrameRate: Int,
269
- val shouldSendPcm: Boolean,
270
- val shouldSendLc3: Boolean,
271
- val shouldSendTranscript: Boolean,
272
- val bypassVad: Boolean,
273
- val offlineCaptionsRunning: Boolean,
274
- val localSttFallbackActive: Boolean,
275
- val shouldSendBootingMessage: Boolean,
276
- ) {
277
- val defaultDevice: Device?
278
- get() =
279
- defaultWearable.takeIf { it.isNotBlank() }?.let {
280
- Device(
281
- model = DeviceModel.fromDeviceType(it),
282
- name = deviceName,
283
- address = deviceAddress.takeIf(String::isNotBlank),
284
- )
285
- }
286
-
287
- internal fun toMap(): Map<String, Any> =
288
- mapOf(
289
- "searching" to searching,
290
- "searchingController" to searchingController,
291
- "systemMicUnavailable" to systemMicUnavailable,
292
- "micEnabled" to micEnabled,
293
- "currentMic" to currentMic,
294
- "micRanking" to micRanking,
295
- "searchResults" to searchResults.map(Device::toMap),
296
- "wifiScanResults" to wifiScanResults.map { it.toMap() },
297
- "lastLog" to lastLog,
298
- "otherBtConnected" to otherBtConnected,
299
- "default_wearable" to defaultWearable,
300
- "pending_wearable" to pendingWearable,
301
- "device_name" to deviceName,
302
- "device_address" to deviceAddress,
303
- "default_controller" to defaultController,
304
- "pending_controller" to pendingController,
305
- "controller_device_name" to controllerDeviceName,
306
- "screen_disabled" to screenDisabled,
307
- "preferred_mic" to preferredMic,
308
- "sensing_enabled" to sensingEnabled,
309
- "power_saving_mode" to powerSavingMode,
310
- "brightness" to brightness,
311
- "auto_brightness" to autoBrightness,
312
- "dashboard_height" to dashboardHeight,
313
- "dashboard_depth" to dashboardDepth,
314
- "head_up_angle" to headUpAngle,
315
- "contextual_dashboard" to contextualDashboard,
316
- "galleryModeAuto" to galleryModeAuto,
317
- "button_photo_size" to buttonPhotoSize.value,
318
- "button_camera_led" to buttonCameraLed,
319
- "button_max_recording_time" to buttonMaxRecordingTime,
320
- "button_video_width" to buttonVideoWidth,
321
- "button_video_height" to buttonVideoHeight,
322
- "button_video_fps" to buttonVideoFrameRate,
323
- "should_send_pcm" to shouldSendPcm,
324
- "should_send_lc3" to shouldSendLc3,
325
- "should_send_transcript" to shouldSendTranscript,
326
- "bypass_vad" to bypassVad,
327
- "offline_captions_running" to offlineCaptionsRunning,
328
- "local_stt_fallback_active" to localSttFallbackActive,
329
- "shouldSendBootingMessage" to shouldSendBootingMessage,
330
- )
331
-
332
- companion object {
333
- internal fun fromMap(values: Map<String, Any>): BluetoothStatus =
334
- BluetoothStatus(
335
- searching = boolValue(values, "searching") ?: false,
336
- searchingController = boolValue(values, "searchingController") ?: false,
337
- systemMicUnavailable = boolValue(values, "systemMicUnavailable") ?: false,
338
- micEnabled = boolValue(values, "micEnabled") ?: false,
339
- currentMic = stringValue(values, "currentMic") ?: "",
340
- micRanking = stringListValue(values, "micRanking"),
341
- searchResults =
342
- mapListValue(values, "searchResults").mapNotNull(Device::fromMap),
343
- wifiScanResults =
344
- mapListValue(values, "wifiScanResults").map(WifiScanResult::fromMap),
345
- lastLog = stringListValue(values, "lastLog"),
346
- otherBtConnected = boolValue(values, "otherBtConnected") ?: false,
347
- defaultWearable = stringValue(values, "default_wearable") ?: "",
348
- pendingWearable = stringValue(values, "pending_wearable") ?: "",
349
- deviceName = stringValue(values, "device_name") ?: "",
350
- deviceAddress = stringValue(values, "device_address") ?: "",
351
- defaultController = stringValue(values, "default_controller") ?: "",
352
- pendingController = stringValue(values, "pending_controller") ?: "",
353
- controllerDeviceName = stringValue(values, "controller_device_name") ?: "",
354
- screenDisabled = boolValue(values, "screen_disabled") ?: false,
355
- preferredMic = stringValue(values, "preferred_mic") ?: "auto",
356
- sensingEnabled = boolValue(values, "sensing_enabled") ?: true,
357
- powerSavingMode = boolValue(values, "power_saving_mode") ?: false,
358
- brightness = numberValue(values, "brightness") ?: 50,
359
- autoBrightness = boolValue(values, "auto_brightness") ?: true,
360
- dashboardHeight = numberValue(values, "dashboard_height") ?: 4,
361
- dashboardDepth = numberValue(values, "dashboard_depth") ?: 2,
362
- headUpAngle = numberValue(values, "head_up_angle") ?: 30,
363
- contextualDashboard = boolValue(values, "contextual_dashboard") ?: true,
364
- galleryModeAuto = boolValue(values, "galleryModeAuto") ?: true,
365
- buttonPhotoSize = ButtonPhotoSize.fromValue(stringValue(values, "button_photo_size")),
366
- buttonCameraLed = boolValue(values, "button_camera_led") ?: true,
367
- buttonMaxRecordingTime = numberValue(values, "button_max_recording_time") ?: 10,
368
- buttonVideoWidth = numberValue(values, "button_video_width") ?: 1280,
369
- buttonVideoHeight = numberValue(values, "button_video_height") ?: 720,
370
- buttonVideoFrameRate = numberValue(values, "button_video_fps") ?: 30,
371
- shouldSendPcm = boolValue(values, "should_send_pcm") ?: false,
372
- shouldSendLc3 = boolValue(values, "should_send_lc3") ?: false,
373
- shouldSendTranscript = boolValue(values, "should_send_transcript") ?: false,
374
- bypassVad = boolValue(values, "bypass_vad") ?: false,
375
- offlineCaptionsRunning = boolValue(values, "offline_captions_running") ?: false,
376
- localSttFallbackActive = boolValue(values, "local_stt_fallback_active") ?: false,
377
- shouldSendBootingMessage = boolValue(values, "shouldSendBootingMessage") ?: true,
378
- )
379
- }
380
- }
381
-
382
- data class GlassesStatusUpdate(
383
- val fullyBooted: Boolean? = null,
384
- val connected: Boolean? = null,
385
- val micEnabled: Boolean? = null,
386
- val connectionState: GlassesConnectionState? = null,
387
- val bluetoothClassicConnected: Boolean? = null,
388
- val signalStrength: Int? = null,
389
- val signalStrengthUpdatedAt: Long? = null,
390
- val deviceModel: String? = null,
391
- val androidVersion: String? = null,
392
- val firmwareVersion: String? = null,
393
- val besFirmwareVersion: String? = null,
394
- val mtkFirmwareVersion: String? = null,
395
- val bluetoothMacAddress: String? = null,
396
- val leftMacAddress: String? = null,
397
- val rightMacAddress: String? = null,
398
- val macAddress: String? = null,
399
- val buildNumber: String? = null,
400
- val otaVersionUrl: String? = null,
401
- val appVersion: String? = null,
402
- val bluetoothName: String? = null,
403
- val serialNumber: String? = null,
404
- val style: String? = null,
405
- val color: String? = null,
406
- val wifi: WifiStatus? = null,
407
- val batteryLevel: Int? = null,
408
- val charging: Boolean? = null,
409
- val caseBatteryLevel: Int? = null,
410
- val caseCharging: Boolean? = null,
411
- val caseOpen: Boolean? = null,
412
- val caseRemoved: Boolean? = null,
413
- val hotspot: HotspotStatus? = null,
414
- val headUp: Boolean? = null,
415
- val controllerConnected: Boolean? = null,
416
- val controllerFullyBooted: Boolean? = null,
417
- val controllerMacAddress: String? = null,
418
- val controllerBatteryLevel: Int? = null,
419
- val controllerSignalStrength: Int? = null,
420
- val ringSignalStrength: Int? = null,
421
- ) {
422
- internal fun toMap(): Map<String, Any> =
423
- buildMap {
424
- if (fullyBooted != null || connected != null || connectionState != null) {
425
- val state =
426
- connectionState
427
- ?: if (connected == true || fullyBooted == true) {
428
- GlassesConnectionState.CONNECTED
429
- } else {
430
- GlassesConnectionState.DISCONNECTED
431
- }
432
- put("connection", state.toStatusMap(connected == true, fullyBooted == true))
433
- }
434
- putIfNotNull("micEnabled", micEnabled)
435
- putIfNotNull("bluetoothClassicConnected", bluetoothClassicConnected)
436
- putIfNotNull("signalStrength", signalStrength)
437
- putIfNotNull("signalStrengthUpdatedAt", signalStrengthUpdatedAt)
438
- putIfNotNull("deviceModel", deviceModel)
439
- putIfNotNull("androidVersion", androidVersion)
440
- putIfNotNull("firmwareVersion", firmwareVersion)
441
- putIfNotNull("besFirmwareVersion", besFirmwareVersion)
442
- putIfNotNull("mtkFirmwareVersion", mtkFirmwareVersion)
443
- putIfNotNull("bluetoothMacAddress", bluetoothMacAddress)
444
- putIfNotNull("leftMacAddress", leftMacAddress)
445
- putIfNotNull("rightMacAddress", rightMacAddress)
446
- putIfNotNull("macAddress", macAddress)
447
- putIfNotNull("buildNumber", buildNumber)
448
- putIfNotNull("otaVersionUrl", otaVersionUrl)
449
- putIfNotNull("appVersion", appVersion)
450
- putIfNotNull("bluetoothName", bluetoothName)
451
- putIfNotNull("serialNumber", serialNumber)
452
- putIfNotNull("style", style)
453
- putIfNotNull("color", color)
454
- wifi?.let {
455
- put("wifi", it.toMap())
456
- }
457
- putIfNotNull("batteryLevel", batteryLevel)
458
- putIfNotNull("charging", charging)
459
- putIfNotNull("caseBatteryLevel", caseBatteryLevel)
460
- putIfNotNull("caseCharging", caseCharging)
461
- putIfNotNull("caseOpen", caseOpen)
462
- putIfNotNull("caseRemoved", caseRemoved)
463
- hotspot?.let {
464
- put("hotspot", it.toMap())
465
- }
466
- putIfNotNull("headUp", headUp)
467
- putIfNotNull("controllerConnected", controllerConnected)
468
- putIfNotNull("controllerFullyBooted", controllerFullyBooted)
469
- putIfNotNull("controllerMacAddress", controllerMacAddress)
470
- putIfNotNull("controllerBatteryLevel", controllerBatteryLevel)
471
- putIfNotNull("controllerSignalStrength", controllerSignalStrength)
472
- putIfNotNull("ringSignalStrength", ringSignalStrength)
473
- }
474
-
475
- companion object {
476
- internal fun fromMap(values: Map<String, Any>): GlassesStatusUpdate =
477
- GlassesStatusUpdate(
478
- fullyBooted = optionalBoolValue(values, "fullyBooted"),
479
- connected = optionalBoolValue(values, "connected"),
480
- micEnabled = optionalBoolValue(values, "micEnabled"),
481
- connectionState = GlassesConnectionState.optionalFromValue(optionalStringValue(values, "connectionState")),
482
- bluetoothClassicConnected = optionalBoolValue(values, "bluetoothClassicConnected"),
483
- signalStrength = optionalNumberValue(values, "signalStrength"),
484
- signalStrengthUpdatedAt = optionalLongValue(values, "signalStrengthUpdatedAt"),
485
- deviceModel = optionalStringValue(values, "deviceModel"),
486
- androidVersion = optionalStringValue(values, "androidVersion"),
487
- firmwareVersion = optionalStringValue(values, "firmwareVersion"),
488
- besFirmwareVersion = optionalStringValue(values, "besFirmwareVersion"),
489
- mtkFirmwareVersion = optionalStringValue(values, "mtkFirmwareVersion"),
490
- bluetoothMacAddress = optionalStringValue(values, "bluetoothMacAddress"),
491
- leftMacAddress = optionalStringValue(values, "leftMacAddress"),
492
- rightMacAddress = optionalStringValue(values, "rightMacAddress"),
493
- macAddress = optionalStringValue(values, "macAddress"),
494
- buildNumber = optionalStringValue(values, "buildNumber"),
495
- otaVersionUrl = optionalStringValue(values, "otaVersionUrl"),
496
- appVersion = optionalStringValue(values, "appVersion"),
497
- bluetoothName = optionalStringValue(values, "bluetoothName"),
498
- serialNumber = optionalStringValue(values, "serialNumber"),
499
- style = optionalStringValue(values, "style"),
500
- color = optionalStringValue(values, "color"),
501
- wifi =
502
- if (hasAnyKey(values, "wifi")) {
503
- WifiStatus.fromMap(values)
504
- } else if (hasAnyKey(values, "wifiConnected", "wifiSsid", "wifiLocalIp")) {
505
- WifiStatus.fromStoreMap(values)
506
- } else {
507
- null
508
- },
509
- batteryLevel = optionalNumberValue(values, "batteryLevel"),
510
- charging = optionalBoolValue(values, "charging"),
511
- caseBatteryLevel = optionalNumberValue(values, "caseBatteryLevel"),
512
- caseCharging = optionalBoolValue(values, "caseCharging"),
513
- caseOpen = optionalBoolValue(values, "caseOpen"),
514
- caseRemoved = optionalBoolValue(values, "caseRemoved"),
515
- hotspot =
516
- if (hasAnyKey(values, "hotspot")) {
517
- HotspotStatus.fromMap(values)
518
- } else if (hasAnyKey(values, "hotspotEnabled", "hotspotSsid", "hotspotPassword", "hotspotGatewayIp")) {
519
- HotspotStatus.fromStoreMap(values)
520
- } else {
521
- null
522
- },
523
- headUp = optionalBoolValue(values, "headUp"),
524
- controllerConnected = optionalBoolValue(values, "controllerConnected"),
525
- controllerFullyBooted = optionalBoolValue(values, "controllerFullyBooted"),
526
- controllerMacAddress = optionalStringValue(values, "controllerMacAddress"),
527
- controllerBatteryLevel = optionalNumberValue(values, "controllerBatteryLevel"),
528
- controllerSignalStrength = optionalNumberValue(values, "controllerSignalStrength"),
529
- ringSignalStrength = optionalNumberValue(values, "ringSignalStrength"),
530
- )
531
- }
532
- }
533
-
534
- data class BluetoothStatusUpdate(
535
- val searching: Boolean? = null,
536
- val searchingController: Boolean? = null,
537
- val systemMicUnavailable: Boolean? = null,
538
- val micEnabled: Boolean? = null,
539
- val currentMic: String? = null,
540
- val micRanking: List<String>? = null,
541
- /**
542
- * Nearby glasses in stable discovery order when included in an update. Existing entries keep
543
- * their array position as details refresh; new glasses append at the end, and removals should
544
- * not reorder remaining entries.
545
- */
546
- val searchResults: List<Device>? = null,
547
- val wifiScanResults: List<WifiScanResult>? = null,
548
- val lastLog: List<String>? = null,
549
- val otherBtConnected: Boolean? = null,
550
- val defaultWearable: String? = null,
551
- val pendingWearable: String? = null,
552
- val deviceName: String? = null,
553
- val deviceAddress: String? = null,
554
- val defaultController: String? = null,
555
- val pendingController: String? = null,
556
- val controllerDeviceName: String? = null,
557
- val screenDisabled: Boolean? = null,
558
- val preferredMic: String? = null,
559
- val sensingEnabled: Boolean? = null,
560
- val powerSavingMode: Boolean? = null,
561
- val brightness: Int? = null,
562
- val autoBrightness: Boolean? = null,
563
- val dashboardHeight: Int? = null,
564
- val dashboardDepth: Int? = null,
565
- val headUpAngle: Int? = null,
566
- val contextualDashboard: Boolean? = null,
567
- val galleryModeAuto: Boolean? = null,
568
- val buttonPhotoSize: ButtonPhotoSize? = null,
569
- val buttonCameraLed: Boolean? = null,
570
- val buttonMaxRecordingTime: Int? = null,
571
- val buttonVideoWidth: Int? = null,
572
- val buttonVideoHeight: Int? = null,
573
- val buttonVideoFrameRate: Int? = null,
574
- val shouldSendPcm: Boolean? = null,
575
- val shouldSendLc3: Boolean? = null,
576
- val shouldSendTranscript: Boolean? = null,
577
- val bypassVad: Boolean? = null,
578
- val offlineCaptionsRunning: Boolean? = null,
579
- val localSttFallbackActive: Boolean? = null,
580
- val shouldSendBootingMessage: Boolean? = null,
581
- ) {
582
- internal fun toMap(): Map<String, Any> =
583
- buildMap {
584
- putIfNotNull("searching", searching)
585
- putIfNotNull("searchingController", searchingController)
586
- putIfNotNull("systemMicUnavailable", systemMicUnavailable)
587
- putIfNotNull("micEnabled", micEnabled)
588
- putIfNotNull("currentMic", currentMic)
589
- putIfNotNull("micRanking", micRanking)
590
- searchResults?.let { put("searchResults", it.map(Device::toMap)) }
591
- wifiScanResults?.let { put("wifiScanResults", it.map(WifiScanResult::toMap)) }
592
- putIfNotNull("lastLog", lastLog)
593
- putIfNotNull("otherBtConnected", otherBtConnected)
594
- putIfNotNull("default_wearable", defaultWearable)
595
- putIfNotNull("pending_wearable", pendingWearable)
596
- putIfNotNull("device_name", deviceName)
597
- putIfNotNull("device_address", deviceAddress)
598
- putIfNotNull("default_controller", defaultController)
599
- putIfNotNull("pending_controller", pendingController)
600
- putIfNotNull("controller_device_name", controllerDeviceName)
601
- putIfNotNull("screen_disabled", screenDisabled)
602
- putIfNotNull("preferred_mic", preferredMic)
603
- putIfNotNull("sensing_enabled", sensingEnabled)
604
- putIfNotNull("power_saving_mode", powerSavingMode)
605
- putIfNotNull("brightness", brightness)
606
- putIfNotNull("auto_brightness", autoBrightness)
607
- putIfNotNull("dashboard_height", dashboardHeight)
608
- putIfNotNull("dashboard_depth", dashboardDepth)
609
- putIfNotNull("head_up_angle", headUpAngle)
610
- putIfNotNull("contextual_dashboard", contextualDashboard)
611
- putIfNotNull("galleryModeAuto", galleryModeAuto)
612
- buttonPhotoSize?.let { put("button_photo_size", it.value) }
613
- putIfNotNull("button_camera_led", buttonCameraLed)
614
- putIfNotNull("button_max_recording_time", buttonMaxRecordingTime)
615
- putIfNotNull("button_video_width", buttonVideoWidth)
616
- putIfNotNull("button_video_height", buttonVideoHeight)
617
- putIfNotNull("button_video_fps", buttonVideoFrameRate)
618
- putIfNotNull("should_send_pcm", shouldSendPcm)
619
- putIfNotNull("should_send_lc3", shouldSendLc3)
620
- putIfNotNull("should_send_transcript", shouldSendTranscript)
621
- putIfNotNull("bypass_vad", bypassVad)
622
- putIfNotNull("offline_captions_running", offlineCaptionsRunning)
623
- putIfNotNull("local_stt_fallback_active", localSttFallbackActive)
624
- putIfNotNull("shouldSendBootingMessage", shouldSendBootingMessage)
625
- }
626
-
627
- companion object {
628
- internal fun fromMap(values: Map<String, Any>): BluetoothStatusUpdate =
629
- BluetoothStatusUpdate(
630
- searching = optionalBoolValue(values, "searching"),
631
- searchingController = optionalBoolValue(values, "searchingController"),
632
- systemMicUnavailable = optionalBoolValue(values, "systemMicUnavailable"),
633
- micEnabled = optionalBoolValue(values, "micEnabled"),
634
- currentMic = optionalStringValue(values, "currentMic"),
635
- micRanking = optionalStringListValue(values, "micRanking"),
636
- searchResults =
637
- optionalMapListValue(values, "searchResults")
638
- ?.mapNotNull(Device::fromMap),
639
- wifiScanResults =
640
- optionalMapListValue(values, "wifiScanResults")
641
- ?.map(WifiScanResult::fromMap),
642
- lastLog = optionalStringListValue(values, "lastLog"),
643
- otherBtConnected = optionalBoolValue(values, "otherBtConnected"),
644
- defaultWearable = optionalStringValue(values, "default_wearable"),
645
- pendingWearable = optionalStringValue(values, "pending_wearable"),
646
- deviceName = optionalStringValue(values, "device_name"),
647
- deviceAddress = optionalStringValue(values, "device_address"),
648
- defaultController = optionalStringValue(values, "default_controller"),
649
- pendingController = optionalStringValue(values, "pending_controller"),
650
- controllerDeviceName = optionalStringValue(values, "controller_device_name"),
651
- screenDisabled = optionalBoolValue(values, "screen_disabled"),
652
- preferredMic = optionalStringValue(values, "preferred_mic"),
653
- sensingEnabled = optionalBoolValue(values, "sensing_enabled"),
654
- powerSavingMode = optionalBoolValue(values, "power_saving_mode"),
655
- brightness = optionalNumberValue(values, "brightness"),
656
- autoBrightness = optionalBoolValue(values, "auto_brightness"),
657
- dashboardHeight = optionalNumberValue(values, "dashboard_height"),
658
- dashboardDepth = optionalNumberValue(values, "dashboard_depth"),
659
- headUpAngle = optionalNumberValue(values, "head_up_angle"),
660
- contextualDashboard = optionalBoolValue(values, "contextual_dashboard"),
661
- galleryModeAuto = optionalBoolValue(values, "galleryModeAuto"),
662
- buttonPhotoSize =
663
- optionalStringValue(values, "button_photo_size")?.let(ButtonPhotoSize::fromValue),
664
- buttonCameraLed = optionalBoolValue(values, "button_camera_led"),
665
- buttonMaxRecordingTime = optionalNumberValue(values, "button_max_recording_time"),
666
- buttonVideoWidth = optionalNumberValue(values, "button_video_width"),
667
- buttonVideoHeight = optionalNumberValue(values, "button_video_height"),
668
- buttonVideoFrameRate = optionalNumberValue(values, "button_video_fps"),
669
- shouldSendPcm = optionalBoolValue(values, "should_send_pcm"),
670
- shouldSendLc3 = optionalBoolValue(values, "should_send_lc3"),
671
- shouldSendTranscript = optionalBoolValue(values, "should_send_transcript"),
672
- bypassVad = optionalBoolValue(values, "bypass_vad"),
673
- offlineCaptionsRunning = optionalBoolValue(values, "offline_captions_running"),
674
- localSttFallbackActive = optionalBoolValue(values, "local_stt_fallback_active"),
675
- shouldSendBootingMessage = optionalBoolValue(values, "shouldSendBootingMessage"),
676
- )
677
- }
678
- }
679
-
680
- data class DisplayTextRequest(
681
- val text: String,
682
- val x: Int = 0,
683
- val y: Int = 0,
684
- val size: Int = 24,
685
- ) {
686
- fun toMap(): Map<String, Any> =
687
- mapOf(
688
- "text" to text,
689
- "x" to x,
690
- "y" to y,
691
- "size" to size,
692
- )
693
- }
694
-
695
- internal data class DisplayEventRequest(
696
- val values: Map<String, Any>,
697
- ) {
698
- fun toMap(): Map<String, Any> = values
699
- }
700
-
701
- data class DashboardPositionRequest(
702
- val height: Int,
703
- val depth: Int,
704
- )
705
-
706
- internal data class DashboardMenuItem(
707
- val title: String,
708
- val packageName: String,
709
- val values: Map<String, Any> = emptyMap(),
710
- ) {
711
- fun toMap(): Map<String, Any> =
712
- values + mapOf(
713
- "title" to title,
714
- "packageName" to packageName,
715
- )
716
- }
717
-
718
- enum class GalleryMode {
719
- AUTO,
720
- MANUAL,
721
- }
722
-
723
- enum class PhotoSize(val value: String) {
724
- SMALL("small"),
725
- MEDIUM("medium"),
726
- LARGE("large"),
727
- FULL("full");
728
-
729
- companion object {
730
- @JvmStatic
731
- fun fromValue(value: String?): PhotoSize =
732
- values().firstOrNull { it.value == value } ?: MEDIUM
733
- }
734
- }
735
-
736
- enum class ButtonPhotoSize(val value: String) {
737
- SMALL("small"),
738
- MEDIUM("medium"),
739
- LARGE("large");
740
-
741
- companion object {
742
- @JvmStatic
743
- fun fromValue(value: String?): ButtonPhotoSize =
744
- values().firstOrNull { it.value == value } ?: MEDIUM
745
- }
746
- }
747
-
748
- enum class PhotoCompression(val value: String) {
749
- NONE("none"),
750
- MEDIUM("medium"),
751
- HEAVY("heavy");
752
-
753
- companion object {
754
- @JvmStatic
755
- fun fromValue(value: String?): PhotoCompression =
756
- values().firstOrNull { it.value == value } ?: NONE
757
- }
758
- }
759
-
760
- data class ButtonPhotoSettings(
761
- val size: ButtonPhotoSize,
762
- )
763
-
764
- data class ButtonVideoRecordingSettings(
765
- val width: Int,
766
- val height: Int,
767
- val frameRate: Int,
768
- )
769
-
770
- enum class CameraFov(val fov: Int, val roiPosition: Int) {
771
- STANDARD(118, 0),
772
- WIDE(118, 0),
773
- }
774
-
775
- enum class MicPreference(val value: String) {
776
- AUTO("auto"),
777
- PHONE("phone"),
778
- GLASSES("glasses"),
779
- BLUETOOTH("bluetooth"),
780
- }
781
-
782
- data class PhotoRequest @JvmOverloads constructor(
783
- val requestId: String,
784
- val appId: String,
785
- val size: PhotoSize,
786
- val webhookUrl: String,
787
- val authToken: String? = null,
788
- val compress: PhotoCompression = PhotoCompression.MEDIUM,
789
- val sound: Boolean = true,
790
- )
791
-
792
- data class StreamVideoConfig @JvmOverloads constructor(
793
- val width: Int? = null,
794
- val height: Int? = null,
795
- val bitrate: Int? = null,
796
- val frameRate: Int? = null,
797
- ) {
798
- fun toMap(): Map<String, Any> =
799
- listOfNotNull(
800
- width?.let { "width" to it },
801
- height?.let { "height" to it },
802
- bitrate?.let { "bitrate" to it },
803
- frameRate?.let { "frameRate" to it },
804
- ).toMap()
805
-
806
- companion object {
807
- @JvmStatic
808
- fun fromMap(values: Map<String, Any>?): StreamVideoConfig? {
809
- values ?: return null
810
- return StreamVideoConfig(
811
- width = numberValue(values, "width"),
812
- height = numberValue(values, "height"),
813
- bitrate = numberValue(values, "bitrate"),
814
- frameRate = numberValue(values, "frameRate"),
815
- )
816
- }
817
- }
818
- }
819
-
820
- data class StreamAudioConfig @JvmOverloads constructor(
821
- val bitrate: Int? = null,
822
- val sampleRate: Int? = null,
823
- val echoCancellation: Boolean? = null,
824
- val noiseSuppression: Boolean? = null,
825
- ) {
826
- fun toMap(): Map<String, Any> =
827
- listOfNotNull(
828
- bitrate?.let { "bitrate" to it },
829
- sampleRate?.let { "sampleRate" to it },
830
- echoCancellation?.let { "echoCancellation" to it },
831
- noiseSuppression?.let { "noiseSuppression" to it },
832
- ).toMap()
833
-
834
- companion object {
835
- @JvmStatic
836
- fun fromMap(values: Map<String, Any>?): StreamAudioConfig? {
837
- values ?: return null
838
- return StreamAudioConfig(
839
- bitrate = numberValue(values, "bitrate"),
840
- sampleRate = numberValue(values, "sampleRate"),
841
- echoCancellation = values["echoCancellation"] as? Boolean,
842
- noiseSuppression = values["noiseSuppression"] as? Boolean,
843
- )
844
- }
845
- }
846
- }
847
-
848
- data class StreamRequest @JvmOverloads constructor(
849
- val streamUrl: String,
850
- val streamId: String = "",
851
- val keepAlive: Boolean = true,
852
- val keepAliveIntervalSeconds: Int = 15,
853
- val sound: Boolean = true,
854
- val video: StreamVideoConfig? = null,
855
- val audio: StreamAudioConfig? = null,
856
- val extraValues: Map<String, Any> = emptyMap(),
857
- ) {
858
- fun toMap(): Map<String, Any> {
859
- val values = extraValues.toMutableMap()
860
- values["type"] = "start_stream"
861
- values["streamUrl"] = streamUrl
862
- values["streamId"] = streamId
863
- values["keepAlive"] = keepAlive
864
- values["keepAliveIntervalSeconds"] = keepAliveIntervalSeconds
865
- // The camera light is a privacy indicator and cannot be disabled by SDK callers.
866
- values["flash"] = true
867
- values["sound"] = sound
868
- video?.toMap()?.takeIf { it.isNotEmpty() }?.let { values["video"] = it }
869
- audio?.toMap()?.takeIf { it.isNotEmpty() }?.let { values["audio"] = it }
870
- return values
871
- }
872
-
873
- companion object {
874
- @JvmStatic
875
- fun fromMap(values: Map<String, Any>): StreamRequest =
876
- StreamRequest(
877
- streamUrl =
878
- (values["streamUrl"] ?: values["rtmpUrl"] ?: values["srtUrl"] ?: values["whipUrl"]) as? String
879
- ?: "",
880
- streamId = values["streamId"] as? String ?: "",
881
- keepAlive = values["keepAlive"] as? Boolean ?: true,
882
- keepAliveIntervalSeconds = (values["keepAliveIntervalSeconds"] as? Number)?.toInt() ?: 15,
883
- sound = values["sound"] as? Boolean ?: true,
884
- video = StreamVideoConfig.fromMap(stringMapValue(values["video"])),
885
- audio = StreamAudioConfig.fromMap(stringMapValue(values["audio"])),
886
- extraValues = values,
887
- )
888
- }
889
- }
890
-
891
- data class StreamKeepAliveRequest @JvmOverloads constructor(
892
- val streamId: String,
893
- val ackId: String,
894
- val extraValues: Map<String, Any> = emptyMap(),
895
- ) {
896
- fun toMap(): Map<String, Any> {
897
- val values = extraValues.toMutableMap()
898
- values["type"] = "keep_stream_alive"
899
- values["streamId"] = streamId
900
- values["ackId"] = ackId
901
- return values
902
- }
903
-
904
- companion object {
905
- @JvmStatic
906
- fun fromMap(values: Map<String, Any>): StreamKeepAliveRequest =
907
- StreamKeepAliveRequest(
908
- streamId = values["streamId"] as? String ?: "",
909
- ackId = values["ackId"] as? String ?: "",
910
- extraValues = values,
911
- )
912
- }
913
- }
914
-
915
- enum class RgbLedAction(val value: String) {
916
- ON("on"),
917
- OFF("off");
918
-
919
- companion object {
920
- @JvmStatic
921
- fun fromValue(value: String?): RgbLedAction =
922
- values().firstOrNull { it.value == value } ?: OFF
923
- }
924
- }
925
-
926
- enum class RgbLedColor(val value: String) {
927
- RED("red"),
928
- GREEN("green"),
929
- BLUE("blue"),
930
- ORANGE("orange"),
931
- WHITE("white");
932
-
933
- companion object {
934
- @JvmStatic
935
- fun fromValue(value: String?): RgbLedColor? =
936
- values().firstOrNull { it.value == value }
937
- }
938
- }
939
-
940
- data class RgbLedRequest @JvmOverloads constructor(
941
- val requestId: String,
942
- val packageName: String?,
943
- val action: RgbLedAction,
944
- val color: RgbLedColor?,
945
- val onDurationMs: Int,
946
- val offDurationMs: Int,
947
- val count: Int,
948
- )
949
-
950
- data class VideoRecordingRequest(
951
- val requestId: String,
952
- val save: Boolean,
953
- val sound: Boolean,
954
- )
955
-
956
- data class ButtonPressEvent(
957
- val buttonId: String,
958
- val pressType: String,
959
- val timestamp: Long? = null,
960
- )
961
-
962
- data class TouchEvent(
963
- val values: Map<String, Any>,
964
- ) {
965
- val deviceModel: String? get() = stringValue(values, "deviceModel")
966
- val gestureName: String? get() = stringValue(values, "gestureName")
967
- val timestamp: Long? get() = longValue(values, "timestamp")
968
- val isSwipe: Boolean get() = gestureName?.contains("swipe", ignoreCase = true) == true
969
- }
970
-
971
- data class SwipeEvent(
972
- val values: Map<String, Any>,
973
- ) {
974
- val deviceModel: String? get() = stringValue(values, "deviceModel")
975
- val gestureName: String? get() = stringValue(values, "gestureName")
976
- val timestamp: Long? get() = longValue(values, "timestamp")
977
- }
978
-
979
- data class BatteryStatusEvent(
980
- val level: Int?,
981
- val charging: Boolean?,
982
- val values: Map<String, Any>,
983
- )
984
-
985
- sealed interface WifiStatus {
986
- val state: String
987
-
988
- fun toMap(): Map<String, Any> =
989
- when (this) {
990
- is Connected -> {
991
- val values =
992
- mutableMapOf<String, Any>(
993
- "state" to state,
994
- "ssid" to ssid,
995
- )
996
- localIp?.let { values["localIp"] = it }
997
- values
998
- }
999
- Disconnected -> mapOf("state" to state)
1000
- }
1001
-
1002
- fun toEventMap(): Map<String, Any> =
1003
- toMap() + mapOf("type" to "wifi_status_change")
1004
-
1005
- object Disconnected : WifiStatus {
1006
- override val state: String = "disconnected"
1007
- }
1008
-
1009
- data class Connected(
1010
- val ssid: String,
1011
- val localIp: String?,
1012
- ) : WifiStatus {
1013
- override val state: String = "connected"
1014
- }
1015
-
1016
- companion object {
1017
- internal fun fromMap(values: Map<String, Any>): WifiStatus? {
1018
- val wifiValues = (values["wifi"] as? Map<*, *>)?.stringKeyedMap() ?: values
1019
- return when (stringValue(wifiValues, "state")?.lowercase()) {
1020
- "connected" -> connectedFrom(wifiValues)
1021
- "disconnected" -> Disconnected
1022
- else -> null
1023
- }
1024
- }
1025
-
1026
- internal fun fromStoreMap(values: Map<String, Any>): WifiStatus? {
1027
- val connected = boolValue(values, "wifiConnected") ?: return null
1028
- return fromStoreFields(
1029
- connected = connected,
1030
- ssid = stringValue(values, "wifiSsid"),
1031
- localIp = stringValue(values, "wifiLocalIp"),
1032
- )
1033
- }
1034
-
1035
- internal fun fromStoreFields(connected: Boolean, ssid: String?, localIp: String?): WifiStatus? {
1036
- if (!connected) return Disconnected
1037
- val nonEmptySsid = ssid?.trim()?.takeIf { it.isNotEmpty() }
1038
- val nonEmptyLocalIp = localIp?.trim()?.takeIf { it.isNotEmpty() }
1039
- return nonEmptySsid?.let { Connected(it, nonEmptyLocalIp) }
1040
- }
1041
-
1042
- private fun connectedFrom(values: Map<String, Any>): WifiStatus? =
1043
- fromStoreFields(
1044
- connected = true,
1045
- ssid = stringValue(values, "ssid"),
1046
- localIp = stringValue(values, "localIp"),
1047
- )
1048
- }
1049
- }
1050
-
1051
- data class WifiStatusEvent(
1052
- val status: WifiStatus,
1053
- ) {
1054
- internal constructor(values: Map<String, Any>) : this(WifiStatus.fromMap(values) ?: WifiStatus.Disconnected)
1055
- internal constructor(connected: Boolean, ssid: String?, localIp: String?) : this(
1056
- WifiStatus.fromStoreFields(connected, ssid, localIp) ?: WifiStatus.Disconnected
1057
- )
1058
-
1059
- val values: Map<String, Any> get() = status.toEventMap()
1060
- }
1061
-
1062
- sealed interface HotspotStatus {
1063
- val state: String
1064
-
1065
- fun toMap(): Map<String, Any> =
1066
- when (this) {
1067
- is Enabled ->
1068
- mapOf(
1069
- "state" to state,
1070
- "ssid" to ssid,
1071
- "password" to password,
1072
- "localIp" to localIp,
1073
- )
1074
- Disabled -> mapOf("state" to state)
1075
- }
1076
-
1077
- fun toEventMap(): Map<String, Any> =
1078
- toMap() + mapOf("type" to "hotspot_status_change")
1079
-
1080
- object Disabled : HotspotStatus {
1081
- override val state: String = "disabled"
1082
- }
1083
-
1084
- data class Enabled(
1085
- val ssid: String,
1086
- val password: String,
1087
- val localIp: String,
1088
- ) : HotspotStatus {
1089
- override val state: String = "enabled"
1090
- }
1091
-
1092
- companion object {
1093
- internal fun fromMap(values: Map<String, Any>): HotspotStatus? {
1094
- val hotspotValues = (values["hotspot"] as? Map<*, *>)?.stringKeyedMap() ?: values
1095
- return when (stringValue(hotspotValues, "state")?.lowercase()) {
1096
- "enabled" -> enabledFrom(hotspotValues)
1097
- "disabled" -> Disabled
1098
- else -> null
1099
- }
1100
- }
1101
-
1102
- internal fun fromStoreMap(values: Map<String, Any>): HotspotStatus? {
1103
- val enabled = boolValue(values, "hotspotEnabled") ?: return null
1104
- return fromStoreFields(
1105
- enabled = enabled,
1106
- ssid = stringValue(values, "hotspotSsid"),
1107
- password = stringValue(values, "hotspotPassword"),
1108
- localIp = stringValue(values, "hotspotGatewayIp"),
1109
- )
1110
- }
1111
-
1112
- internal fun fromStoreFields(
1113
- enabled: Boolean,
1114
- ssid: String?,
1115
- password: String?,
1116
- localIp: String?,
1117
- ): HotspotStatus? {
1118
- if (!enabled) return Disabled
1119
- val nonEmptySsid = ssid?.trim()?.takeIf { it.isNotEmpty() }
1120
- val nonEmptyPassword = password?.trim()?.takeIf { it.isNotEmpty() }
1121
- val nonEmptyLocalIp = localIp?.trim()?.takeIf { it.isNotEmpty() }
1122
- return if (nonEmptySsid != null && nonEmptyPassword != null && nonEmptyLocalIp != null) {
1123
- Enabled(nonEmptySsid, nonEmptyPassword, nonEmptyLocalIp)
1124
- } else {
1125
- null
1126
- }
1127
- }
1128
-
1129
- private fun enabledFrom(values: Map<String, Any>): HotspotStatus? =
1130
- fromStoreFields(
1131
- enabled = true,
1132
- ssid = stringValue(values, "ssid"),
1133
- password = stringValue(values, "password"),
1134
- localIp = stringValue(values, "localIp"),
1135
- )
1136
- }
1137
- }
1138
-
1139
- data class HotspotStatusEvent(
1140
- val status: HotspotStatus,
1141
- ) {
1142
- internal constructor(values: Map<String, Any>) : this(HotspotStatus.fromMap(values) ?: HotspotStatus.Disabled)
1143
- internal constructor(enabled: Boolean, ssid: String?, password: String?, localIp: String?) : this(
1144
- HotspotStatus.fromStoreFields(enabled, ssid, password, localIp) ?: HotspotStatus.Disabled
1145
- )
1146
-
1147
- val values: Map<String, Any> get() = status.toEventMap()
1148
- }
1149
-
1150
- data class HotspotErrorEvent(
1151
- val values: Map<String, Any>,
1152
- ) {
1153
- val message: String? get() = stringValue(values, "errorMessage")
1154
- val timestamp: Long? get() = longValue(values, "timestamp")
1155
- }
1156
-
1157
- data class GalleryStatusEvent(
1158
- val values: Map<String, Any>,
1159
- )
1160
-
1161
- sealed interface PhotoResponse {
1162
- val state: String
1163
- val requestId: String
1164
- val timestamp: Long
1165
-
1166
- fun toMap(): Map<String, Any> =
1167
- when (this) {
1168
- is Success -> mapOf(
1169
- "state" to state,
1170
- "requestId" to requestId,
1171
- "uploadUrl" to uploadUrl,
1172
- "timestamp" to timestamp,
1173
- )
1174
-
1175
- is Error -> mutableMapOf<String, Any>(
1176
- "state" to state,
1177
- "requestId" to requestId,
1178
- "timestamp" to timestamp,
1179
- "errorMessage" to errorMessage,
1180
- ).apply {
1181
- if (!errorCode.isNullOrBlank()) {
1182
- this["errorCode"] = errorCode
1183
- }
1184
- }
1185
- }
1186
-
1187
- fun toEventMap(): Map<String, Any> = toMap() + mapOf("type" to "photo_response")
1188
-
1189
- data class Success(
1190
- override val requestId: String,
1191
- val uploadUrl: String,
1192
- override val timestamp: Long,
1193
- ) : PhotoResponse {
1194
- override val state: String = "success"
1195
- }
1196
-
1197
- data class Error(
1198
- override val requestId: String,
1199
- val errorCode: String?,
1200
- val errorMessage: String,
1201
- override val timestamp: Long,
1202
- ) : PhotoResponse {
1203
- override val state: String = "error"
1204
- }
1205
-
1206
- companion object {
1207
- fun fromMap(values: Map<String, Any>): PhotoResponse {
1208
- val requestId = stringValue(values, "requestId").orEmpty()
1209
- val timestamp = longValue(values, "timestamp") ?: System.currentTimeMillis()
1210
- val state = stringValue(values, "state")?.lowercase()
1211
- return if (state == "success") {
1212
- val uploadUrl = stringValue(values, "uploadUrl").orEmpty()
1213
- Success(requestId = requestId, uploadUrl = uploadUrl, timestamp = timestamp)
1214
- } else {
1215
- Error(
1216
- requestId = requestId,
1217
- errorCode = stringValue(values, "errorCode"),
1218
- errorMessage = stringValue(values, "errorMessage") ?: "Unknown photo error",
1219
- timestamp = timestamp,
1220
- )
1221
- }
1222
- }
1223
- }
1224
- }
1225
-
1226
- data class PhotoResponseEvent(
1227
- val response: PhotoResponse,
1228
- ) {
1229
- constructor(values: Map<String, Any>) : this(PhotoResponse.fromMap(values))
1230
-
1231
- val requestId: String get() = response.requestId
1232
- val values: Map<String, Any> get() = response.toEventMap()
1233
- }
1234
-
1235
- enum class StreamState(val value: String) {
1236
- INITIALIZING("initializing"),
1237
- STREAMING("streaming"),
1238
- STOPPING("stopping"),
1239
- STOPPED("stopped"),
1240
- RECONNECTING("reconnecting"),
1241
- RECONNECTED("reconnected"),
1242
- RECONNECT_FAILED("reconnect_failed"),
1243
- ERROR("error");
1244
-
1245
- companion object {
1246
- @JvmStatic
1247
- fun fromValue(value: String?): StreamState? =
1248
- when (value?.lowercase()) {
1249
- "initializing", "starting", "connecting" -> INITIALIZING
1250
- "streaming", "streaming_started", "active" -> STREAMING
1251
- "stopping" -> STOPPING
1252
- "stopped", "not_streaming", "disconnected", "timeout" -> STOPPED
1253
- "reconnecting" -> RECONNECTING
1254
- "reconnected" -> RECONNECTED
1255
- "reconnect_failed" -> RECONNECT_FAILED
1256
- "error", "error_not_streaming" -> ERROR
1257
- else -> null
1258
- }
1259
- }
1260
- }
1261
-
1262
- enum class StreamStatusKind(val value: String) {
1263
- LIFECYCLE("lifecycle"),
1264
- RECONNECT("reconnect"),
1265
- ERROR("error"),
1266
- SNAPSHOT("snapshot"),
1267
- }
1268
-
1269
- sealed interface StreamStatus {
1270
- val kind: StreamStatusKind
1271
- val state: StreamState
1272
- val streamId: String?
1273
- val timestamp: Long?
1274
-
1275
- fun toMap(): Map<String, Any> {
1276
- val values = mutableMapOf<String, Any>(
1277
- "kind" to kind.value,
1278
- "status" to state.value,
1279
- )
1280
- streamId?.takeIf { it.isNotBlank() }?.let { values["streamId"] = it }
1281
- timestamp?.let { values["timestamp"] = it }
1282
-
1283
- when (this) {
1284
- is Lifecycle -> Unit
1285
- is Reconnecting -> {
1286
- values["attempt"] = attempt
1287
- values["maxAttempts"] = maxAttempts
1288
- values["reason"] = reason
1289
- }
1290
- is Reconnected -> values["attempt"] = attempt
1291
- is ReconnectFailed -> values["maxAttempts"] = maxAttempts
1292
- is Error -> values["errorDetails"] = errorDetails
1293
- is Snapshot -> {
1294
- values["streaming"] = streaming
1295
- values["reconnecting"] = reconnecting
1296
- attempt?.let { values["attempt"] = it }
1297
- }
1298
- }
1299
-
1300
- return values
1301
- }
1302
-
1303
- fun toEventMap(): Map<String, Any> = toMap() + mapOf("type" to "stream_status")
1304
-
1305
- data class Lifecycle(
1306
- override val state: StreamState,
1307
- override val streamId: String?,
1308
- override val timestamp: Long?,
1309
- ) : StreamStatus {
1310
- override val kind: StreamStatusKind = StreamStatusKind.LIFECYCLE
1311
- }
1312
-
1313
- data class Reconnecting(
1314
- override val streamId: String?,
1315
- val attempt: Int,
1316
- val maxAttempts: Int,
1317
- val reason: String,
1318
- override val timestamp: Long?,
1319
- ) : StreamStatus {
1320
- override val kind: StreamStatusKind = StreamStatusKind.RECONNECT
1321
- override val state: StreamState = StreamState.RECONNECTING
1322
- }
1323
-
1324
- data class Reconnected(
1325
- override val streamId: String?,
1326
- val attempt: Int,
1327
- override val timestamp: Long?,
1328
- ) : StreamStatus {
1329
- override val kind: StreamStatusKind = StreamStatusKind.RECONNECT
1330
- override val state: StreamState = StreamState.RECONNECTED
1331
- }
1332
-
1333
- data class ReconnectFailed(
1334
- override val streamId: String?,
1335
- val maxAttempts: Int,
1336
- override val timestamp: Long?,
1337
- ) : StreamStatus {
1338
- override val kind: StreamStatusKind = StreamStatusKind.RECONNECT
1339
- override val state: StreamState = StreamState.RECONNECT_FAILED
1340
- }
1341
-
1342
- data class Error(
1343
- override val streamId: String?,
1344
- val errorDetails: String,
1345
- override val timestamp: Long?,
1346
- ) : StreamStatus {
1347
- override val kind: StreamStatusKind = StreamStatusKind.ERROR
1348
- override val state: StreamState = StreamState.ERROR
1349
- }
1350
-
1351
- data class Snapshot(
1352
- override val state: StreamState,
1353
- val streaming: Boolean,
1354
- val reconnecting: Boolean,
1355
- override val streamId: String?,
1356
- val attempt: Int?,
1357
- override val timestamp: Long?,
1358
- ) : StreamStatus {
1359
- override val kind: StreamStatusKind = StreamStatusKind.SNAPSHOT
1360
- }
1361
-
1362
- companion object {
1363
- @JvmStatic
1364
- fun fromMap(values: Map<String, Any>): StreamStatus {
1365
- val rawState = stringValue(values, "status")
1366
- val streaming = boolValue(values, "streaming")
1367
- val reconnecting = boolValue(values, "reconnecting") ?: false
1368
- val streamId = stringValue(values, "streamId")
1369
- val timestamp = longValue(values, "timestamp")
1370
- val attempt = numberValue(values, "attempt")
1371
- val maxAttempts = numberValue(values, "maxAttempts") ?: 0
1372
-
1373
- if (streaming != null || hasAnyKey(values, "reconnecting")) {
1374
- return Snapshot(
1375
- state = when {
1376
- reconnecting -> StreamState.RECONNECTING
1377
- streaming == true -> StreamState.STREAMING
1378
- else -> StreamState.STOPPED
1379
- },
1380
- streaming = streaming == true,
1381
- reconnecting = reconnecting,
1382
- streamId = streamId,
1383
- attempt = attempt,
1384
- timestamp = timestamp,
1385
- )
1386
- }
1387
-
1388
- val state = StreamState.fromValue(rawState)
1389
- ?: return Error(
1390
- streamId = streamId,
1391
- errorDetails = rawState?.let { "Unknown stream status: $it" } ?: "Missing stream status",
1392
- timestamp = timestamp,
1393
- )
1394
-
1395
- return when (state) {
1396
- StreamState.RECONNECTING -> Reconnecting(
1397
- streamId = streamId,
1398
- attempt = attempt ?: 0,
1399
- maxAttempts = maxAttempts,
1400
- reason = stringValue(values, "reason") ?: "",
1401
- timestamp = timestamp,
1402
- )
1403
- StreamState.RECONNECTED -> Reconnected(
1404
- streamId = streamId,
1405
- attempt = attempt ?: 0,
1406
- timestamp = timestamp,
1407
- )
1408
- StreamState.RECONNECT_FAILED -> ReconnectFailed(
1409
- streamId = streamId,
1410
- maxAttempts = maxAttempts,
1411
- timestamp = timestamp,
1412
- )
1413
- StreamState.ERROR -> Error(
1414
- streamId = streamId,
1415
- errorDetails = stringValue(values, "errorDetails")
1416
- ?: if (rawState == "error_not_streaming") "not_streaming" else "Unknown stream error",
1417
- timestamp = timestamp,
1418
- )
1419
- else -> Lifecycle(
1420
- state = state,
1421
- streamId = streamId,
1422
- timestamp = timestamp,
1423
- )
1424
- }
1425
- }
1426
- }
1427
- }
1428
-
1429
- data class StreamStatusEvent(
1430
- val status: StreamStatus,
1431
- ) {
1432
- constructor(values: Map<String, Any>) : this(StreamStatus.fromMap(values))
1433
-
1434
- val state: StreamState get() = status.state
1435
- val streamId: String? get() = status.streamId
1436
- val values: Map<String, Any> get() = status.toEventMap()
1437
- }
1438
-
1439
- data class KeepAliveAckEvent(
1440
- val streamId: String,
1441
- val ackId: String,
1442
- val timestamp: Long?,
1443
- ) {
1444
- constructor(values: Map<String, Any>) : this(
1445
- streamId = stringValue(values, "streamId").orEmpty(),
1446
- ackId = stringValue(values, "ackId").orEmpty(),
1447
- timestamp = longValue(values, "timestamp"),
1448
- )
1449
-
1450
- val values: Map<String, Any>
1451
- get() = buildMap {
1452
- put("type", "keep_alive_ack")
1453
- put("streamId", streamId)
1454
- put("ackId", ackId)
1455
- timestamp?.let { put("timestamp", it) }
1456
- }
1457
- }
1458
-
1459
- data class LocalTranscriptionEvent(
1460
- val text: String,
1461
- val isFinal: Boolean,
1462
- val values: Map<String, Any>,
1463
- )
1464
-
1465
- data class GlassesMediaVolumeGetResult(
1466
- val level: Int?,
1467
- val statusCode: Int?,
1468
- val values: Map<String, Any>,
1469
- ) {
1470
- companion object {
1471
- fun fromMap(values: Map<String, Any>): GlassesMediaVolumeGetResult =
1472
- GlassesMediaVolumeGetResult(
1473
- level = numberValue(values, "level"),
1474
- statusCode = (values["statusCode"] as? Number)?.toInt(),
1475
- values = values,
1476
- )
1477
- }
1478
- }
1479
-
1480
- data class GlassesMediaVolumeSetResult(
1481
- val statusCode: Int?,
1482
- val values: Map<String, Any>,
1483
- ) {
1484
- companion object {
1485
- fun fromMap(values: Map<String, Any>): GlassesMediaVolumeSetResult =
1486
- GlassesMediaVolumeSetResult(
1487
- statusCode = (values["statusCode"] as? Number)?.toInt(),
1488
- values = values,
1489
- )
1490
- }
1491
- }
1492
-
1493
- enum class GlassesConnectionState(val value: String) {
1494
- DISCONNECTED(ConnTypes.DISCONNECTED),
1495
- SCANNING(ConnTypes.SCANNING),
1496
- CONNECTING(ConnTypes.CONNECTING),
1497
- BONDING(ConnTypes.BONDING),
1498
- CONNECTED(ConnTypes.CONNECTED);
1499
-
1500
- val isConnected: Boolean
1501
- get() = this == CONNECTED
1502
-
1503
- val isBusy: Boolean
1504
- get() = this == SCANNING || this == CONNECTING || this == BONDING
1505
-
1506
- internal fun toStatusMap(
1507
- connected: Boolean,
1508
- fullyBooted: Boolean,
1509
- ): Map<String, Any> =
1510
- when {
1511
- this == CONNECTED || connected || fullyBooted ->
1512
- mapOf("state" to "connected", "fullyBooted" to fullyBooted)
1513
- this == SCANNING -> mapOf("state" to "scanning")
1514
- this == CONNECTING -> mapOf("state" to "connecting")
1515
- this == BONDING -> mapOf("state" to "bonding")
1516
- else -> mapOf("state" to "disconnected")
1517
- }
1518
-
1519
- companion object {
1520
- @JvmStatic
1521
- fun fromValue(value: String?): GlassesConnectionState =
1522
- optionalFromValue(value) ?: DISCONNECTED
1523
-
1524
- internal fun optionalFromValue(value: String?): GlassesConnectionState? {
1525
- val normalized = value?.trim()?.takeIf { it.isNotEmpty() } ?: return null
1526
- return values().firstOrNull { it.value.equals(normalized, ignoreCase = true) }
1527
- }
1528
- }
1529
- }
1530
-
1531
- data class BluetoothError(
1532
- val code: String,
1533
- val message: String,
1534
- val cause: Throwable? = null,
1535
- )
1536
-
1537
- enum class ScanStopReason {
1538
- COMPLETED,
1539
- CANCELLED,
1540
- ERROR,
1541
- }
1542
-
1543
- interface ScanCallback {
1544
- fun onResults(devices: List<Device>) {}
1545
- fun onComplete(devices: List<Device>) {}
1546
- fun onError(error: BluetoothError) {}
1547
- }
1548
-
1549
- abstract class MentraBluetoothScanCallback : ScanCallback
1550
-
1551
- class ScanSession internal constructor(
1552
- private val stopAction: () -> Unit,
1553
- ) {
1554
- @Volatile
1555
- private var stopped = false
1556
-
1557
- fun stop() {
1558
- if (stopped) return
1559
- stopped = true
1560
- stopAction()
1561
- }
1562
-
1563
- internal fun markStopped() {
1564
- stopped = true
1565
- }
1566
- }
1567
-
1568
- interface MentraBluetoothSdkListener {
1569
- fun onGlassesStatusChanged(status: GlassesStatusUpdate) {}
1570
- fun onBluetoothStatusChanged(status: BluetoothStatusUpdate) {}
1571
- fun onDeviceDiscovered(device: Device) {}
1572
- fun onScanStopped(reason: ScanStopReason) {}
1573
- fun onButtonPress(event: ButtonPressEvent) {}
1574
- fun onTouch(event: TouchEvent) {}
1575
- fun onSwipe(event: SwipeEvent) {}
1576
- fun onHeadUpChanged(headUp: Boolean) {}
1577
- fun onBatteryStatus(event: BatteryStatusEvent) {}
1578
- fun onWifiStatusChanged(event: WifiStatusEvent) {}
1579
- fun onHotspotStatusChanged(event: HotspotStatusEvent) {}
1580
- fun onHotspotError(event: HotspotErrorEvent) {}
1581
- fun onGalleryStatus(event: GalleryStatusEvent) {}
1582
- fun onPhotoResponse(event: PhotoResponseEvent) {}
1583
- fun onStreamStatus(event: StreamStatusEvent) {}
1584
- fun onKeepAliveAck(event: KeepAliveAckEvent) {}
1585
- fun onMicPcm(frame: ByteArray) {}
1586
- fun onMicLc3(frame: ByteArray) {}
1587
- fun onLocalTranscription(event: LocalTranscriptionEvent) {}
1588
- fun onDefaultDeviceChanged(device: Device?) {}
1589
- fun onLog(message: String) {}
1590
- fun onError(error: BluetoothError) {}
1591
- fun onRawEvent(eventName: String, values: Map<String, Any>) {}
1592
- }
1593
-
1594
- abstract class MentraBluetoothSdkCallback : MentraBluetoothSdkListener
1595
-
1596
- private fun numberValue(
1597
- values: Map<String, Any>,
1598
- vararg keys: String,
1599
- ): Int? =
1600
- keys.firstNotNullOfOrNull { key ->
1601
- (values[key] as? Number)?.toInt()
1602
- }
1603
-
1604
- private fun stringValue(
1605
- values: Map<String, Any>,
1606
- vararg keys: String,
1607
- ): String? =
1608
- keys.firstNotNullOfOrNull { key ->
1609
- values[key]?.let { it as? String }
1610
- }
1611
-
1612
- private fun boolValue(
1613
- values: Map<String, Any>,
1614
- vararg keys: String,
1615
- ): Boolean? =
1616
- keys.firstNotNullOfOrNull { key ->
1617
- values[key] as? Boolean
1618
- }
1619
-
1620
- private fun longValue(
1621
- values: Map<String, Any>,
1622
- vararg keys: String,
1623
- ): Long? =
1624
- keys.firstNotNullOfOrNull { key ->
1625
- (values[key] as? Number)?.toLong()
1626
- }
1627
-
1628
- private fun hasAnyKey(
1629
- values: Map<String, Any>,
1630
- vararg keys: String,
1631
- ): Boolean = keys.any(values::containsKey)
1632
-
1633
- private fun Map<*, *>.stringKeyedMap(): Map<String, Any> =
1634
- entries.mapNotNull { (key, value) ->
1635
- val stringKey = key as? String ?: return@mapNotNull null
1636
- val anyValue = value ?: return@mapNotNull null
1637
- stringKey to anyValue
1638
- }.toMap()
1639
-
1640
- private fun optionalNumberValue(
1641
- values: Map<String, Any>,
1642
- vararg keys: String,
1643
- ): Int? =
1644
- if (hasAnyKey(values, *keys)) {
1645
- numberValue(values, *keys)
1646
- } else {
1647
- null
1648
- }
1649
-
1650
- private fun optionalLongValue(
1651
- values: Map<String, Any>,
1652
- vararg keys: String,
1653
- ): Long? =
1654
- if (hasAnyKey(values, *keys)) {
1655
- longValue(values, *keys)
1656
- } else {
1657
- null
1658
- }
1659
-
1660
- private fun optionalStringValue(
1661
- values: Map<String, Any>,
1662
- vararg keys: String,
1663
- ): String? =
1664
- if (hasAnyKey(values, *keys)) {
1665
- stringValue(values, *keys) ?: ""
1666
- } else {
1667
- null
1668
- }
1669
-
1670
- private fun optionalBoolValue(
1671
- values: Map<String, Any>,
1672
- vararg keys: String,
1673
- ): Boolean? =
1674
- if (hasAnyKey(values, *keys)) {
1675
- boolValue(values, *keys) ?: false
1676
- } else {
1677
- null
1678
- }
1679
-
1680
- private fun stringListValue(
1681
- values: Map<String, Any>,
1682
- key: String,
1683
- ): List<String> = (values[key] as? List<*>)?.mapNotNull { it as? String } ?: emptyList()
1684
-
1685
- private fun optionalStringListValue(
1686
- values: Map<String, Any>,
1687
- key: String,
1688
- ): List<String>? =
1689
- if (values.containsKey(key)) {
1690
- stringListValue(values, key)
1691
- } else {
1692
- null
1693
- }
1694
-
1695
- private fun mapListValue(
1696
- values: Map<String, Any>,
1697
- key: String,
1698
- ): List<Map<String, Any>> =
1699
- (values[key] as? List<*>)?.mapNotNull(::stringMapValue) ?: emptyList()
1700
-
1701
- private fun optionalMapListValue(
1702
- values: Map<String, Any>,
1703
- key: String,
1704
- ): List<Map<String, Any>>? =
1705
- if (values.containsKey(key)) {
1706
- mapListValue(values, key)
1707
- } else {
1708
- null
1709
- }
1710
-
1711
- private fun stringMapValue(value: Any?): Map<String, Any>? =
1712
- (value as? Map<*, *>)?.entries?.mapNotNull { (key, mapValue) ->
1713
- val stringKey = key as? String ?: return@mapNotNull null
1714
- val nonNullValue = mapValue ?: return@mapNotNull null
1715
- stringKey to nonNullValue
1716
- }?.toMap()
1717
-
1718
- private fun MutableMap<String, Any>.putIfNotNull(
1719
- key: String,
1720
- value: Any?,
1721
- ) {
1722
- if (value != null) {
1723
- put(key, value)
1724
- }
1725
- }