@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
@@ -0,0 +1,58 @@
1
+ import Foundation
2
+
3
+ public struct DisplayTextRequest {
4
+ public let text: String
5
+ public let x: Int
6
+ public let y: Int
7
+ public let size: Int
8
+
9
+ public init(text: String, x: Int = 0, y: Int = 0, size: Int = 24) {
10
+ self.text = text
11
+ self.x = x
12
+ self.y = y
13
+ self.size = size
14
+ }
15
+
16
+ var dictionary: [String: Any] {
17
+ [
18
+ "text": text,
19
+ "x": x,
20
+ "y": y,
21
+ "size": size,
22
+ ]
23
+ }
24
+ }
25
+
26
+ struct DisplayEventRequest {
27
+ let values: [String: Any]
28
+
29
+ init(values: [String: Any]) {
30
+ self.values = values
31
+ }
32
+ }
33
+
34
+ public struct DashboardPositionRequest {
35
+ public let height: Int
36
+ public let depth: Int
37
+
38
+ public init(height: Int, depth: Int) {
39
+ self.height = height
40
+ self.depth = depth
41
+ }
42
+ }
43
+
44
+ struct DashboardMenuItem {
45
+ let title: String
46
+ let packageName: String
47
+ let values: [String: Any]
48
+
49
+ init(title: String, packageName: String, values: [String: Any] = [:]) {
50
+ self.title = title
51
+ self.packageName = packageName
52
+ self.values = values
53
+ }
54
+
55
+ var dictionary: [String: Any] {
56
+ values.merging(["title": title, "packageName": packageName]) { _, new in new }
57
+ }
58
+ }
@@ -0,0 +1,463 @@
1
+ import Foundation
2
+
3
+ public struct WifiScanResult: CustomStringConvertible {
4
+ public let ssid: String
5
+ public let requiresPassword: Bool
6
+ public let signalStrength: Int
7
+ public let frequency: Int?
8
+
9
+ public init(ssid: String, requiresPassword: Bool, signalStrength: Int, frequency: Int? = nil) {
10
+ self.ssid = ssid
11
+ self.requiresPassword = requiresPassword
12
+ self.signalStrength = signalStrength
13
+ self.frequency = frequency
14
+ }
15
+
16
+ init(values: [String: Any]) {
17
+ ssid = stringValue(values, "ssid") ?? ""
18
+ requiresPassword = boolValue(values, "requiresPassword") ?? false
19
+ signalStrength = intValue(values["signalStrength"]) ?? -1
20
+ frequency = intValue(values["frequency"])
21
+ }
22
+
23
+ var dictionary: [String: Any] {
24
+ var values: [String: Any] = [
25
+ "ssid": ssid,
26
+ "requiresPassword": requiresPassword,
27
+ "signalStrength": signalStrength,
28
+ ]
29
+ if let frequency {
30
+ values["frequency"] = frequency
31
+ }
32
+ return values
33
+ }
34
+
35
+ public var description: String {
36
+ "WifiScanResult(ssid: \(ssid), signalStrength: \(signalStrength))"
37
+ }
38
+ }
39
+
40
+ public enum GlassesConnectionState: String, CustomStringConvertible, Equatable {
41
+ case disconnected = "DISCONNECTED"
42
+ case scanning = "SCANNING"
43
+ case connecting = "CONNECTING"
44
+ case bonding = "BONDING"
45
+ case connected = "CONNECTED"
46
+
47
+ public init(_ value: String?) {
48
+ self = Self.fromValue(value) ?? .disconnected
49
+ }
50
+
51
+ public static func fromValue(_ value: String?) -> GlassesConnectionState? {
52
+ guard let normalized = value?.trimmingCharacters(in: .whitespacesAndNewlines).uppercased(),
53
+ !normalized.isEmpty
54
+ else {
55
+ return nil
56
+ }
57
+ return Self(rawValue: normalized)
58
+ }
59
+
60
+ public var isConnected: Bool {
61
+ self == .connected
62
+ }
63
+
64
+ public var isBusy: Bool {
65
+ self == .scanning || self == .connecting || self == .bonding
66
+ }
67
+
68
+ func statusValues(connected: Bool, fullyBooted: Bool) -> [String: Any] {
69
+ if self == .connected || connected || fullyBooted {
70
+ return ["state": "connected", "fullyBooted": fullyBooted]
71
+ }
72
+ switch self {
73
+ case .scanning:
74
+ return ["state": "scanning"]
75
+ case .connecting:
76
+ return ["state": "connecting"]
77
+ case .bonding:
78
+ return ["state": "bonding"]
79
+ case .connected:
80
+ return ["state": "connected", "fullyBooted": fullyBooted]
81
+ case .disconnected:
82
+ return ["state": "disconnected"]
83
+ }
84
+ }
85
+
86
+ public var description: String {
87
+ rawValue
88
+ }
89
+ }
90
+
91
+ struct GlassesStatus: CustomStringConvertible {
92
+ let values: [String: Any]
93
+
94
+ init(values: [String: Any]) {
95
+ self.values = values
96
+ }
97
+
98
+ func applying(_ update: GlassesStatusUpdate) -> GlassesStatus {
99
+ GlassesStatus(values: values.merging(update.values) { _, new in new })
100
+ }
101
+
102
+ func withBattery(level: Int, charging: Bool) -> GlassesStatus {
103
+ applying(GlassesStatusUpdate(values: ["batteryLevel": level, "charging": charging]))
104
+ }
105
+
106
+ func withWifi(_ wifi: WifiStatus) -> GlassesStatus {
107
+ applying(GlassesStatusUpdate(values: wifi.storeValues))
108
+ }
109
+
110
+ func withHotspot(_ hotspot: HotspotStatus) -> GlassesStatus {
111
+ applying(GlassesStatusUpdate(values: hotspot.storeValues))
112
+ }
113
+
114
+ func disconnected() -> GlassesStatus {
115
+ applying(GlassesStatusUpdate(values: [
116
+ "connected": false,
117
+ "connectionState": "DISCONNECTED",
118
+ "fullyBooted": false,
119
+ "batteryLevel": -1,
120
+ "charging": false,
121
+ "hotspotEnabled": false,
122
+ "hotspotGatewayIp": "",
123
+ "hotspotPassword": "",
124
+ "hotspotSsid": "",
125
+ "wifiConnected": false,
126
+ "wifiSsid": "",
127
+ "wifiLocalIp": "",
128
+ "signalStrength": -1,
129
+ "signalStrengthUpdatedAt": 0,
130
+ ]))
131
+ }
132
+
133
+ var fullyBooted: Bool { boolValue(values, "fullyBooted") ?? false }
134
+ var connected: Bool { boolValue(values, "connected") ?? false }
135
+ var micEnabled: Bool { boolValue(values, "micEnabled") ?? false }
136
+ var connectionState: GlassesConnectionState { GlassesConnectionState(stringValue(values, "connectionState")) }
137
+ var bluetoothClassicConnected: Bool { boolValue(values, "bluetoothClassicConnected") ?? false }
138
+ var signalStrength: Int { intValue(values["signalStrength"]) ?? -1 }
139
+ var signalStrengthUpdatedAt: Int { intValue(values["signalStrengthUpdatedAt"]) ?? 0 }
140
+ var deviceModel: String { stringValue(values, "deviceModel") ?? "" }
141
+ var androidVersion: String { stringValue(values, "androidVersion") ?? "" }
142
+ var firmwareVersion: String { stringValue(values, "firmwareVersion") ?? "" }
143
+ var besFirmwareVersion: String { stringValue(values, "besFirmwareVersion") ?? "" }
144
+ var mtkFirmwareVersion: String { stringValue(values, "mtkFirmwareVersion") ?? "" }
145
+ var bluetoothMacAddress: String { stringValue(values, "bluetoothMacAddress") ?? "" }
146
+ var leftMacAddress: String { stringValue(values, "leftMacAddress") ?? "" }
147
+ var rightMacAddress: String { stringValue(values, "rightMacAddress") ?? "" }
148
+ var macAddress: String { stringValue(values, "macAddress") ?? "" }
149
+ var buildNumber: String { stringValue(values, "buildNumber") ?? "" }
150
+ var otaVersionUrl: String { stringValue(values, "otaVersionUrl") ?? "" }
151
+ var appVersion: String { stringValue(values, "appVersion") ?? "" }
152
+ var bluetoothName: String { stringValue(values, "bluetoothName") ?? "" }
153
+ var serialNumber: String { stringValue(values, "serialNumber") ?? "" }
154
+ var style: String { stringValue(values, "style") ?? "" }
155
+ var color: String { stringValue(values, "color") ?? "" }
156
+ var wifi: WifiStatus { WifiStatus.fromStoreValues(values) ?? .disconnected }
157
+ var hotspot: HotspotStatus { HotspotStatus.fromStoreValues(values) ?? .disabled }
158
+ var dictionary: [String: Any] { Self.dictionary(from: values) }
159
+ var batteryLevel: Int { intValue(values["batteryLevel"]) ?? -1 }
160
+ var charging: Bool { boolValue(values, "charging") ?? false }
161
+ var caseBatteryLevel: Int { intValue(values["caseBatteryLevel"]) ?? -1 }
162
+ var caseCharging: Bool { boolValue(values, "caseCharging") ?? false }
163
+ var caseOpen: Bool { boolValue(values, "caseOpen") ?? true }
164
+ var caseRemoved: Bool { boolValue(values, "caseRemoved") ?? true }
165
+ var headUp: Bool { boolValue(values, "headUp") ?? false }
166
+ var controllerConnected: Bool { boolValue(values, "controllerConnected") ?? false }
167
+ var controllerFullyBooted: Bool { boolValue(values, "controllerFullyBooted") ?? false }
168
+ var controllerMacAddress: String { stringValue(values, "controllerMacAddress") ?? "" }
169
+ var controllerBatteryLevel: Int { intValue(values["controllerBatteryLevel"]) ?? -1 }
170
+ var controllerSignalStrength: Int { intValue(values["controllerSignalStrength"]) ?? -1 }
171
+ var ringSignalStrength: Int { intValue(values["ringSignalStrength"]) ?? -1 }
172
+
173
+ var description: String {
174
+ values.description
175
+ }
176
+
177
+ static func dictionary(from values: [String: Any]) -> [String: Any] {
178
+ var dictionary = values
179
+ dictionary["connection"] = GlassesConnectionState(stringValue(values, "connectionState")).statusValues(
180
+ connected: boolValue(values, "connected") ?? false,
181
+ fullyBooted: boolValue(values, "fullyBooted") ?? false
182
+ )
183
+ dictionary.removeValue(forKey: "connected")
184
+ dictionary.removeValue(forKey: "fullyBooted")
185
+ dictionary.removeValue(forKey: "connectionState")
186
+ dictionary["wifi"] = (WifiStatus.fromStoreValues(values) ?? .disconnected).values
187
+ dictionary["hotspot"] = (HotspotStatus.fromStoreValues(values) ?? .disabled).values
188
+ dictionary.removeValue(forKey: "wifiConnected")
189
+ dictionary.removeValue(forKey: "wifiSsid")
190
+ dictionary.removeValue(forKey: "wifiLocalIp")
191
+ dictionary.removeValue(forKey: "hotspotEnabled")
192
+ dictionary.removeValue(forKey: "hotspotSsid")
193
+ dictionary.removeValue(forKey: "hotspotPassword")
194
+ dictionary.removeValue(forKey: "hotspotGatewayIp")
195
+ return dictionary
196
+ }
197
+
198
+ static func updateDictionary(from values: [String: Any]) -> [String: Any] {
199
+ var dictionary = values
200
+ if hasAnyKey(values, "connection", "connected", "fullyBooted", "connectionState") {
201
+ dictionary["connection"] = (values["connection"] as? [String: Any])
202
+ ?? GlassesConnectionState(stringValue(values, "connectionState")).statusValues(
203
+ connected: boolValue(values, "connected") ?? false,
204
+ fullyBooted: boolValue(values, "fullyBooted") ?? false
205
+ )
206
+ dictionary.removeValue(forKey: "connected")
207
+ dictionary.removeValue(forKey: "fullyBooted")
208
+ dictionary.removeValue(forKey: "connectionState")
209
+ }
210
+ if hasAnyKey(values, "wifi", "wifiConnected", "wifiSsid", "wifiLocalIp") {
211
+ let wifi = (values["wifi"] as? [String: Any]).flatMap(WifiStatus.init(values:))
212
+ ?? WifiStatus.fromStoreValues(values)
213
+ if let wifi {
214
+ dictionary["wifi"] = wifi.values
215
+ }
216
+ dictionary.removeValue(forKey: "wifiConnected")
217
+ dictionary.removeValue(forKey: "wifiSsid")
218
+ dictionary.removeValue(forKey: "wifiLocalIp")
219
+ }
220
+ if hasAnyKey(values, "hotspot") {
221
+ if let hotspot = (values["hotspot"] as? [String: Any]).flatMap(HotspotStatus.init(values:)) {
222
+ dictionary["hotspot"] = hotspot.values
223
+ }
224
+ } else if hasAnyKey(values, "hotspotEnabled", "hotspotSsid", "hotspotPassword", "hotspotGatewayIp") {
225
+ if let hotspot = HotspotStatus.fromStoreValues(values) {
226
+ dictionary["hotspot"] = hotspot.values
227
+ }
228
+ dictionary.removeValue(forKey: "hotspotEnabled")
229
+ dictionary.removeValue(forKey: "hotspotSsid")
230
+ dictionary.removeValue(forKey: "hotspotPassword")
231
+ dictionary.removeValue(forKey: "hotspotGatewayIp")
232
+ }
233
+ return dictionary
234
+ }
235
+ }
236
+
237
+ struct BluetoothStatus: CustomStringConvertible {
238
+ let values: [String: Any]
239
+
240
+ init(values: [String: Any]) {
241
+ self.values = Self.normalized(values)
242
+ }
243
+
244
+ private static func normalized(_ values: [String: Any]) -> [String: Any] {
245
+ var normalizedValues = values
246
+ if let searchResults = values["searchResults"] as? [[String: Any]] {
247
+ normalizedValues["searchResults"] = searchResults.compactMap { Device(values: $0)?.dictionary }
248
+ }
249
+ return normalizedValues
250
+ }
251
+
252
+ func applying(_ update: BluetoothStatusUpdate) -> BluetoothStatus {
253
+ BluetoothStatus(values: values.merging(update.values) { _, new in new })
254
+ }
255
+
256
+ func withDefaultDevice(_ device: Device?) -> BluetoothStatus {
257
+ applying(BluetoothStatusUpdate(values: [
258
+ "default_wearable": device?.model.deviceType ?? "",
259
+ "device_name": device?.name ?? "",
260
+ "device_address": device?.identifier ?? "",
261
+ ]))
262
+ }
263
+
264
+ var searching: Bool { boolValue(values, "searching") ?? false }
265
+ var searchingController: Bool { boolValue(values, "searchingController") ?? false }
266
+ var systemMicUnavailable: Bool { boolValue(values, "systemMicUnavailable") ?? false }
267
+ var micEnabled: Bool { boolValue(values, "micEnabled") ?? false }
268
+ var currentMic: String { stringValue(values, "currentMic") ?? "" }
269
+ var micRanking: [String] { stringListValue(values, "micRanking") }
270
+ /// Nearby glasses in stable discovery order. Existing entries keep their array position as
271
+ /// details refresh; new glasses append at the end, and removals should not reorder remaining entries.
272
+ var searchResults: [Device] {
273
+ dictionaryListValue(values, "searchResults").compactMap(Device.init(values:))
274
+ }
275
+ var wifiScanResults: [WifiScanResult] {
276
+ dictionaryListValue(values, "wifiScanResults").map(WifiScanResult.init(values:))
277
+ }
278
+ var lastLog: [String] { stringListValue(values, "lastLog") }
279
+ var otherBtConnected: Bool { boolValue(values, "otherBtConnected") ?? false }
280
+ var defaultWearable: String { stringValue(values, "default_wearable") ?? "" }
281
+ var pendingWearable: String { stringValue(values, "pending_wearable") ?? "" }
282
+ var deviceName: String { stringValue(values, "device_name") ?? "" }
283
+ var deviceAddress: String { stringValue(values, "device_address") ?? "" }
284
+ var defaultController: String { stringValue(values, "default_controller") ?? "" }
285
+ var pendingController: String { stringValue(values, "pending_controller") ?? "" }
286
+ var controllerDeviceName: String { stringValue(values, "controller_device_name") ?? "" }
287
+ var screenDisabled: Bool { boolValue(values, "screen_disabled") ?? false }
288
+ var preferredMic: String { stringValue(values, "preferred_mic") ?? "auto" }
289
+ var sensingEnabled: Bool { boolValue(values, "sensing_enabled") ?? true }
290
+ var powerSavingMode: Bool { boolValue(values, "power_saving_mode") ?? false }
291
+ var brightness: Int { intValue(values["brightness"]) ?? 50 }
292
+ var autoBrightness: Bool { boolValue(values, "auto_brightness") ?? true }
293
+ var dashboardHeight: Int { intValue(values["dashboard_height"]) ?? 4 }
294
+ var dashboardDepth: Int { intValue(values["dashboard_depth"]) ?? 2 }
295
+ var headUpAngle: Int { intValue(values["head_up_angle"]) ?? 30 }
296
+ var contextualDashboard: Bool { boolValue(values, "contextual_dashboard") ?? true }
297
+ var galleryModeAuto: Bool { boolValue(values, "galleryModeAuto") ?? true }
298
+ var buttonPhotoSize: ButtonPhotoSize {
299
+ ButtonPhotoSize(rawValue: stringValue(values, "button_photo_size") ?? "") ?? .medium
300
+ }
301
+ var buttonCameraLed: Bool { boolValue(values, "button_camera_led") ?? true }
302
+ var buttonMaxRecordingTime: Int { intValue(values["button_max_recording_time"]) ?? 10 }
303
+ var buttonVideoWidth: Int { intValue(values["button_video_width"]) ?? 1280 }
304
+ var buttonVideoHeight: Int { intValue(values["button_video_height"]) ?? 720 }
305
+ var buttonVideoFrameRate: Int { intValue(values["button_video_fps"]) ?? 30 }
306
+ var shouldSendPcm: Bool { boolValue(values, "should_send_pcm") ?? false }
307
+ var shouldSendLc3: Bool { boolValue(values, "should_send_lc3") ?? false }
308
+ var shouldSendTranscript: Bool { boolValue(values, "should_send_transcript") ?? false }
309
+ var bypassVad: Bool { boolValue(values, "bypass_vad") ?? true }
310
+ var offlineCaptionsRunning: Bool { boolValue(values, "offline_captions_running") ?? false }
311
+ var localSttFallbackActive: Bool { boolValue(values, "local_stt_fallback_active") ?? false }
312
+ var shouldSendBootingMessage: Bool { boolValue(values, "shouldSendBootingMessage") ?? true }
313
+
314
+ var defaultDevice: Device? {
315
+ guard !defaultWearable.isEmpty else { return nil }
316
+ return Device(
317
+ model: DeviceModel.fromDeviceType(defaultWearable),
318
+ name: deviceName,
319
+ identifier: deviceAddress.isEmpty ? nil : deviceAddress
320
+ )
321
+ }
322
+
323
+ var description: String {
324
+ values.description
325
+ }
326
+ }
327
+
328
+ struct GlassesStatusUpdate: CustomStringConvertible {
329
+ let values: [String: Any]
330
+
331
+ init(values: [String: Any]) {
332
+ self.values = values
333
+ }
334
+
335
+ var fullyBooted: Bool? { optionalBoolValue(values, "fullyBooted") }
336
+ var connected: Bool? { optionalBoolValue(values, "connected") }
337
+ var micEnabled: Bool? { optionalBoolValue(values, "micEnabled") }
338
+ var connectionState: GlassesConnectionState? {
339
+ GlassesConnectionState.fromValue(optionalStringValue(values, "connectionState"))
340
+ }
341
+ var bluetoothClassicConnected: Bool? { optionalBoolValue(values, "bluetoothClassicConnected") }
342
+ var signalStrength: Int? { optionalIntValue(values, "signalStrength") }
343
+ var signalStrengthUpdatedAt: Int? { optionalIntValue(values, "signalStrengthUpdatedAt") }
344
+ var deviceModel: String? { optionalStringValue(values, "deviceModel") }
345
+ var androidVersion: String? { optionalStringValue(values, "androidVersion") }
346
+ var firmwareVersion: String? { optionalStringValue(values, "firmwareVersion") }
347
+ var besFirmwareVersion: String? { optionalStringValue(values, "besFirmwareVersion") }
348
+ var mtkFirmwareVersion: String? { optionalStringValue(values, "mtkFirmwareVersion") }
349
+ var bluetoothMacAddress: String? { optionalStringValue(values, "bluetoothMacAddress") }
350
+ var leftMacAddress: String? { optionalStringValue(values, "leftMacAddress") }
351
+ var rightMacAddress: String? { optionalStringValue(values, "rightMacAddress") }
352
+ var macAddress: String? { optionalStringValue(values, "macAddress") }
353
+ var buildNumber: String? { optionalStringValue(values, "buildNumber") }
354
+ var otaVersionUrl: String? { optionalStringValue(values, "otaVersionUrl") }
355
+ var appVersion: String? { optionalStringValue(values, "appVersion") }
356
+ var bluetoothName: String? { optionalStringValue(values, "bluetoothName") }
357
+ var serialNumber: String? { optionalStringValue(values, "serialNumber") }
358
+ var style: String? { optionalStringValue(values, "style") }
359
+ var color: String? { optionalStringValue(values, "color") }
360
+ var wifi: WifiStatus? {
361
+ if let wifi = values["wifi"] as? [String: Any] {
362
+ return WifiStatus(values: wifi)
363
+ }
364
+ if hasAnyKey(values, "wifiConnected", "wifiSsid", "wifiLocalIp") {
365
+ return WifiStatus.fromStoreValues(values)
366
+ }
367
+ return nil
368
+ }
369
+ var hotspot: HotspotStatus? {
370
+ if let hotspot = values["hotspot"] as? [String: Any] {
371
+ return HotspotStatus(values: hotspot)
372
+ }
373
+ if hasAnyKey(values, "hotspotEnabled", "hotspotSsid", "hotspotPassword", "hotspotGatewayIp") {
374
+ return HotspotStatus.fromStoreValues(values)
375
+ }
376
+ return nil
377
+ }
378
+ var dictionary: [String: Any] { GlassesStatus.updateDictionary(from: values) }
379
+ var batteryLevel: Int? { optionalIntValue(values, "batteryLevel") }
380
+ var charging: Bool? { optionalBoolValue(values, "charging") }
381
+ var caseBatteryLevel: Int? { optionalIntValue(values, "caseBatteryLevel") }
382
+ var caseCharging: Bool? { optionalBoolValue(values, "caseCharging") }
383
+ var caseOpen: Bool? { optionalBoolValue(values, "caseOpen") }
384
+ var caseRemoved: Bool? { optionalBoolValue(values, "caseRemoved") }
385
+ var headUp: Bool? { optionalBoolValue(values, "headUp") }
386
+ var controllerConnected: Bool? { optionalBoolValue(values, "controllerConnected") }
387
+ var controllerFullyBooted: Bool? { optionalBoolValue(values, "controllerFullyBooted") }
388
+ var controllerMacAddress: String? { optionalStringValue(values, "controllerMacAddress") }
389
+ var controllerBatteryLevel: Int? { optionalIntValue(values, "controllerBatteryLevel") }
390
+ var controllerSignalStrength: Int? { optionalIntValue(values, "controllerSignalStrength") }
391
+ var ringSignalStrength: Int? { optionalIntValue(values, "ringSignalStrength") }
392
+
393
+ var description: String {
394
+ values.description
395
+ }
396
+ }
397
+
398
+ struct BluetoothStatusUpdate: CustomStringConvertible {
399
+ let values: [String: Any]
400
+
401
+ init(values: [String: Any]) {
402
+ var normalizedValues = values
403
+ if let searchResults = values["searchResults"] as? [[String: Any]] {
404
+ normalizedValues["searchResults"] = searchResults.compactMap { Device(values: $0)?.dictionary }
405
+ }
406
+ self.values = normalizedValues
407
+ }
408
+
409
+ var searching: Bool? { optionalBoolValue(values, "searching") }
410
+ var searchingController: Bool? { optionalBoolValue(values, "searchingController") }
411
+ var systemMicUnavailable: Bool? { optionalBoolValue(values, "systemMicUnavailable") }
412
+ var micEnabled: Bool? { optionalBoolValue(values, "micEnabled") }
413
+ var currentMic: String? { optionalStringValue(values, "currentMic") }
414
+ var micRanking: [String]? { optionalStringListValue(values, "micRanking") }
415
+ /// Nearby glasses in stable discovery order when included in an update. Existing entries keep their
416
+ /// array position as details refresh; new glasses append at the end, and removals should not reorder
417
+ /// remaining entries.
418
+ var searchResults: [Device]? {
419
+ optionalDictionaryListValue(values, "searchResults")?.compactMap(Device.init(values:))
420
+ }
421
+ var wifiScanResults: [WifiScanResult]? {
422
+ optionalDictionaryListValue(values, "wifiScanResults")?.map(WifiScanResult.init(values:))
423
+ }
424
+ var lastLog: [String]? { optionalStringListValue(values, "lastLog") }
425
+ var otherBtConnected: Bool? { optionalBoolValue(values, "otherBtConnected") }
426
+ var defaultWearable: String? { optionalStringValue(values, "default_wearable") }
427
+ var pendingWearable: String? { optionalStringValue(values, "pending_wearable") }
428
+ var deviceName: String? { optionalStringValue(values, "device_name") }
429
+ var deviceAddress: String? { optionalStringValue(values, "device_address") }
430
+ var defaultController: String? { optionalStringValue(values, "default_controller") }
431
+ var pendingController: String? { optionalStringValue(values, "pending_controller") }
432
+ var controllerDeviceName: String? { optionalStringValue(values, "controller_device_name") }
433
+ var screenDisabled: Bool? { optionalBoolValue(values, "screen_disabled") }
434
+ var preferredMic: String? { optionalStringValue(values, "preferred_mic") }
435
+ var sensingEnabled: Bool? { optionalBoolValue(values, "sensing_enabled") }
436
+ var powerSavingMode: Bool? { optionalBoolValue(values, "power_saving_mode") }
437
+ var brightness: Int? { optionalIntValue(values, "brightness") }
438
+ var autoBrightness: Bool? { optionalBoolValue(values, "auto_brightness") }
439
+ var dashboardHeight: Int? { optionalIntValue(values, "dashboard_height") }
440
+ var dashboardDepth: Int? { optionalIntValue(values, "dashboard_depth") }
441
+ var headUpAngle: Int? { optionalIntValue(values, "head_up_angle") }
442
+ var contextualDashboard: Bool? { optionalBoolValue(values, "contextual_dashboard") }
443
+ var galleryModeAuto: Bool? { optionalBoolValue(values, "galleryModeAuto") }
444
+ var buttonPhotoSize: ButtonPhotoSize? {
445
+ optionalStringValue(values, "button_photo_size").flatMap(ButtonPhotoSize.init(rawValue:))
446
+ }
447
+ var buttonCameraLed: Bool? { optionalBoolValue(values, "button_camera_led") }
448
+ var buttonMaxRecordingTime: Int? { optionalIntValue(values, "button_max_recording_time") }
449
+ var buttonVideoWidth: Int? { optionalIntValue(values, "button_video_width") }
450
+ var buttonVideoHeight: Int? { optionalIntValue(values, "button_video_height") }
451
+ var buttonVideoFrameRate: Int? { optionalIntValue(values, "button_video_fps") }
452
+ var shouldSendPcm: Bool? { optionalBoolValue(values, "should_send_pcm") }
453
+ var shouldSendLc3: Bool? { optionalBoolValue(values, "should_send_lc3") }
454
+ var shouldSendTranscript: Bool? { optionalBoolValue(values, "should_send_transcript") }
455
+ var bypassVad: Bool? { optionalBoolValue(values, "bypass_vad") }
456
+ var offlineCaptionsRunning: Bool? { optionalBoolValue(values, "offline_captions_running") }
457
+ var localSttFallbackActive: Bool? { optionalBoolValue(values, "local_stt_fallback_active") }
458
+ var shouldSendBootingMessage: Bool? { optionalBoolValue(values, "shouldSendBootingMessage") }
459
+
460
+ var description: String {
461
+ values.description
462
+ }
463
+ }