@mentra/bluetooth-sdk 0.1.5 → 0.1.7

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