@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
@@ -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,471 @@
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 voiceActivityDetectionEnabled: Bool { boolValue(values, "voiceActivityDetectionEnabled") ?? true }
137
+ var connectionState: GlassesConnectionState { GlassesConnectionState(stringValue(values, "connectionState")) }
138
+ var bluetoothClassicConnected: Bool { boolValue(values, "bluetoothClassicConnected") ?? false }
139
+ var signalStrength: Int { intValue(values["signalStrength"]) ?? -1 }
140
+ var signalStrengthUpdatedAt: Int { intValue(values["signalStrengthUpdatedAt"]) ?? 0 }
141
+ var deviceModel: String { stringValue(values, "deviceModel") ?? "" }
142
+ var androidVersion: String { stringValue(values, "androidVersion") ?? "" }
143
+ var firmwareVersion: String { stringValue(values, "firmwareVersion") ?? "" }
144
+ var besFirmwareVersion: String { stringValue(values, "besFirmwareVersion") ?? "" }
145
+ var mtkFirmwareVersion: String { stringValue(values, "mtkFirmwareVersion") ?? "" }
146
+ var bluetoothMacAddress: String { stringValue(values, "bluetoothMacAddress") ?? "" }
147
+ var leftMacAddress: String { stringValue(values, "leftMacAddress") ?? "" }
148
+ var rightMacAddress: String { stringValue(values, "rightMacAddress") ?? "" }
149
+ var macAddress: String { stringValue(values, "macAddress") ?? "" }
150
+ var buildNumber: String { stringValue(values, "buildNumber") ?? "" }
151
+ var otaVersionUrl: String { stringValue(values, "otaVersionUrl") ?? "" }
152
+ var appVersion: String { stringValue(values, "appVersion") ?? "" }
153
+ var bluetoothName: String { stringValue(values, "bluetoothName") ?? "" }
154
+ var serialNumber: String { stringValue(values, "serialNumber") ?? "" }
155
+ var style: String { stringValue(values, "style") ?? "" }
156
+ var color: String { stringValue(values, "color") ?? "" }
157
+ var wifi: WifiStatus { WifiStatus.fromStoreValues(values) ?? .disconnected }
158
+ var hotspot: HotspotStatus { HotspotStatus.fromStoreValues(values) ?? .disabled }
159
+ var dictionary: [String: Any] { Self.dictionary(from: values) }
160
+ var batteryLevel: Int { intValue(values["batteryLevel"]) ?? -1 }
161
+ var charging: Bool { boolValue(values, "charging") ?? false }
162
+ var caseBatteryLevel: Int { intValue(values["caseBatteryLevel"]) ?? -1 }
163
+ var caseCharging: Bool { boolValue(values, "caseCharging") ?? false }
164
+ var caseOpen: Bool { boolValue(values, "caseOpen") ?? true }
165
+ var caseRemoved: Bool { boolValue(values, "caseRemoved") ?? true }
166
+ var headUp: Bool { boolValue(values, "headUp") ?? false }
167
+ var controllerConnected: Bool { boolValue(values, "controllerConnected") ?? false }
168
+ var controllerFullyBooted: Bool { boolValue(values, "controllerFullyBooted") ?? false }
169
+ var controllerMacAddress: String { stringValue(values, "controllerMacAddress") ?? "" }
170
+ var controllerBatteryLevel: Int { intValue(values["controllerBatteryLevel"]) ?? -1 }
171
+ var controllerSignalStrength: Int { intValue(values["controllerSignalStrength"]) ?? -1 }
172
+ var ringSignalStrength: Int { intValue(values["ringSignalStrength"]) ?? -1 }
173
+
174
+ var description: String {
175
+ values.description
176
+ }
177
+
178
+ static func dictionary(from values: [String: Any]) -> [String: Any] {
179
+ var dictionary = values
180
+ dictionary["connection"] = GlassesConnectionState(stringValue(values, "connectionState")).statusValues(
181
+ connected: boolValue(values, "connected") ?? false,
182
+ fullyBooted: boolValue(values, "fullyBooted") ?? false
183
+ )
184
+ dictionary.removeValue(forKey: "connected")
185
+ dictionary.removeValue(forKey: "fullyBooted")
186
+ dictionary.removeValue(forKey: "connectionState")
187
+ dictionary["wifi"] = (WifiStatus.fromStoreValues(values) ?? .disconnected).values
188
+ dictionary["hotspot"] = (HotspotStatus.fromStoreValues(values) ?? .disabled).values
189
+ dictionary.removeValue(forKey: "wifiConnected")
190
+ dictionary.removeValue(forKey: "wifiSsid")
191
+ dictionary.removeValue(forKey: "wifiLocalIp")
192
+ dictionary.removeValue(forKey: "hotspotEnabled")
193
+ dictionary.removeValue(forKey: "hotspotSsid")
194
+ dictionary.removeValue(forKey: "hotspotPassword")
195
+ dictionary.removeValue(forKey: "hotspotGatewayIp")
196
+ return dictionary
197
+ }
198
+
199
+ static func updateDictionary(from values: [String: Any]) -> [String: Any] {
200
+ var dictionary = values
201
+ if hasAnyKey(values, "connection", "connected", "fullyBooted", "connectionState") {
202
+ dictionary["connection"] = (values["connection"] as? [String: Any])
203
+ ?? GlassesConnectionState(stringValue(values, "connectionState")).statusValues(
204
+ connected: boolValue(values, "connected") ?? false,
205
+ fullyBooted: boolValue(values, "fullyBooted") ?? false
206
+ )
207
+ dictionary.removeValue(forKey: "connected")
208
+ dictionary.removeValue(forKey: "fullyBooted")
209
+ dictionary.removeValue(forKey: "connectionState")
210
+ }
211
+ if hasAnyKey(values, "wifi", "wifiConnected", "wifiSsid", "wifiLocalIp") {
212
+ let wifi = (values["wifi"] as? [String: Any]).flatMap(WifiStatus.init(values:))
213
+ ?? WifiStatus.fromStoreValues(values)
214
+ if let wifi {
215
+ dictionary["wifi"] = wifi.values
216
+ }
217
+ dictionary.removeValue(forKey: "wifiConnected")
218
+ dictionary.removeValue(forKey: "wifiSsid")
219
+ dictionary.removeValue(forKey: "wifiLocalIp")
220
+ }
221
+ if hasAnyKey(values, "hotspot") {
222
+ if let hotspot = (values["hotspot"] as? [String: Any]).flatMap(HotspotStatus.init(values:)) {
223
+ dictionary["hotspot"] = hotspot.values
224
+ }
225
+ } else if hasAnyKey(values, "hotspotEnabled", "hotspotSsid", "hotspotPassword", "hotspotGatewayIp") {
226
+ if let hotspot = HotspotStatus.fromStoreValues(values) {
227
+ dictionary["hotspot"] = hotspot.values
228
+ }
229
+ dictionary.removeValue(forKey: "hotspotEnabled")
230
+ dictionary.removeValue(forKey: "hotspotSsid")
231
+ dictionary.removeValue(forKey: "hotspotPassword")
232
+ dictionary.removeValue(forKey: "hotspotGatewayIp")
233
+ }
234
+ return dictionary
235
+ }
236
+ }
237
+
238
+ struct BluetoothStatus: CustomStringConvertible {
239
+ let values: [String: Any]
240
+
241
+ init(values: [String: Any]) {
242
+ self.values = Self.normalized(values)
243
+ }
244
+
245
+ private static func normalized(_ values: [String: Any]) -> [String: Any] {
246
+ var normalizedValues = values
247
+ if let searchResults = values["searchResults"] as? [[String: Any]] {
248
+ normalizedValues["searchResults"] = searchResults.compactMap { Device(values: $0)?.dictionary }
249
+ }
250
+ if let galleryMode = boolValue(values, "gallery_mode") {
251
+ normalizedValues["galleryModeEnabled"] = galleryMode
252
+ normalizedValues.removeValue(forKey: "gallery_mode")
253
+ }
254
+ return normalizedValues
255
+ }
256
+
257
+ func applying(_ update: BluetoothStatusUpdate) -> BluetoothStatus {
258
+ BluetoothStatus(values: values.merging(update.values) { _, new in new })
259
+ }
260
+
261
+ func withDefaultDevice(_ device: Device?) -> BluetoothStatus {
262
+ applying(BluetoothStatusUpdate(values: [
263
+ "default_wearable": device?.model.deviceType ?? "",
264
+ "device_name": device?.name ?? "",
265
+ "device_address": device?.identifier ?? "",
266
+ ]))
267
+ }
268
+
269
+ var searching: Bool { boolValue(values, "searching") ?? false }
270
+ var searchingController: Bool { boolValue(values, "searchingController") ?? false }
271
+ var systemMicUnavailable: Bool { boolValue(values, "systemMicUnavailable") ?? false }
272
+ var micEnabled: Bool { boolValue(values, "micEnabled") ?? false }
273
+ var currentMic: String { stringValue(values, "currentMic") ?? "" }
274
+ var micRanking: [String] { stringListValue(values, "micRanking") }
275
+ /// Nearby glasses in stable discovery order. Existing entries keep their array position as
276
+ /// details refresh; new glasses append at the end, and removals should not reorder remaining entries.
277
+ var searchResults: [Device] {
278
+ dictionaryListValue(values, "searchResults").compactMap(Device.init(values:))
279
+ }
280
+ var wifiScanResults: [WifiScanResult] {
281
+ dictionaryListValue(values, "wifiScanResults").map(WifiScanResult.init(values:))
282
+ }
283
+ var lastLog: [String] { stringListValue(values, "lastLog") }
284
+ var otherBtConnected: Bool { boolValue(values, "otherBtConnected") ?? false }
285
+ var defaultWearable: String { stringValue(values, "default_wearable") ?? "" }
286
+ var pendingWearable: String { stringValue(values, "pending_wearable") ?? "" }
287
+ var deviceName: String { stringValue(values, "device_name") ?? "" }
288
+ var deviceAddress: String { stringValue(values, "device_address") ?? "" }
289
+ var defaultController: String { stringValue(values, "default_controller") ?? "" }
290
+ var pendingController: String { stringValue(values, "pending_controller") ?? "" }
291
+ var controllerDeviceName: String { stringValue(values, "controller_device_name") ?? "" }
292
+ var screenDisabled: Bool { boolValue(values, "screen_disabled") ?? false }
293
+ var preferredMic: String { stringValue(values, "preferred_mic") ?? "auto" }
294
+ var sensingEnabled: Bool { boolValue(values, "sensing_enabled") ?? true }
295
+ var powerSavingMode: Bool { boolValue(values, "power_saving_mode") ?? false }
296
+ var brightness: Int { intValue(values["brightness"]) ?? 50 }
297
+ var autoBrightness: Bool { boolValue(values, "auto_brightness") ?? true }
298
+ var dashboardHeight: Int { intValue(values["dashboard_height"]) ?? 4 }
299
+ var dashboardDepth: Int { intValue(values["dashboard_depth"]) ?? 2 }
300
+ var headUpAngle: Int { intValue(values["head_up_angle"]) ?? 30 }
301
+ var contextualDashboard: Bool { boolValue(values, "contextual_dashboard") ?? true }
302
+ var galleryModeEnabled: Bool { boolValue(values, "gallery_mode") ?? boolValue(values, "galleryModeEnabled") ?? true }
303
+ var buttonPhotoSize: ButtonPhotoSize {
304
+ ButtonPhotoSize(rawValue: stringValue(values, "button_photo_size") ?? "") ?? .medium
305
+ }
306
+ var buttonCameraLed: Bool { boolValue(values, "button_camera_led") ?? true }
307
+ var buttonMaxRecordingTime: Int { intValue(values["button_max_recording_time"]) ?? 10 }
308
+ var buttonVideoWidth: Int { intValue(values["button_video_width"]) ?? 1280 }
309
+ var buttonVideoHeight: Int { intValue(values["button_video_height"]) ?? 720 }
310
+ var buttonVideoFrameRate: Int { intValue(values["button_video_fps"]) ?? 30 }
311
+ var shouldSendPcm: Bool { boolValue(values, "should_send_pcm") ?? false }
312
+ var shouldSendLc3: Bool { boolValue(values, "should_send_lc3") ?? false }
313
+ var shouldSendTranscript: Bool { boolValue(values, "should_send_transcript") ?? false }
314
+ var offlineCaptionsRunning: Bool { boolValue(values, "offline_captions_running") ?? false }
315
+ var localSttFallbackActive: Bool { boolValue(values, "local_stt_fallback_active") ?? false }
316
+ var shouldSendBootingMessage: Bool { boolValue(values, "shouldSendBootingMessage") ?? true }
317
+
318
+ var defaultDevice: Device? {
319
+ guard !defaultWearable.isEmpty else { return nil }
320
+ return Device(
321
+ model: DeviceModel.fromDeviceType(defaultWearable),
322
+ name: deviceName,
323
+ identifier: deviceAddress.isEmpty ? nil : deviceAddress
324
+ )
325
+ }
326
+
327
+ var description: String {
328
+ values.description
329
+ }
330
+ }
331
+
332
+ struct GlassesStatusUpdate: CustomStringConvertible {
333
+ let values: [String: Any]
334
+
335
+ init(values: [String: Any]) {
336
+ self.values = values
337
+ }
338
+
339
+ var fullyBooted: Bool? { optionalBoolValue(values, "fullyBooted") }
340
+ var connected: Bool? { optionalBoolValue(values, "connected") }
341
+ var micEnabled: Bool? { optionalBoolValue(values, "micEnabled") }
342
+ var voiceActivityDetectionEnabled: Bool? { optionalBoolValue(values, "voiceActivityDetectionEnabled") }
343
+ var connectionState: GlassesConnectionState? {
344
+ GlassesConnectionState.fromValue(optionalStringValue(values, "connectionState"))
345
+ }
346
+ var bluetoothClassicConnected: Bool? { optionalBoolValue(values, "bluetoothClassicConnected") }
347
+ var signalStrength: Int? { optionalIntValue(values, "signalStrength") }
348
+ var signalStrengthUpdatedAt: Int? { optionalIntValue(values, "signalStrengthUpdatedAt") }
349
+ var deviceModel: String? { optionalStringValue(values, "deviceModel") }
350
+ var androidVersion: String? { optionalStringValue(values, "androidVersion") }
351
+ var firmwareVersion: String? { optionalStringValue(values, "firmwareVersion") }
352
+ var besFirmwareVersion: String? { optionalStringValue(values, "besFirmwareVersion") }
353
+ var mtkFirmwareVersion: String? { optionalStringValue(values, "mtkFirmwareVersion") }
354
+ var bluetoothMacAddress: String? { optionalStringValue(values, "bluetoothMacAddress") }
355
+ var leftMacAddress: String? { optionalStringValue(values, "leftMacAddress") }
356
+ var rightMacAddress: String? { optionalStringValue(values, "rightMacAddress") }
357
+ var macAddress: String? { optionalStringValue(values, "macAddress") }
358
+ var buildNumber: String? { optionalStringValue(values, "buildNumber") }
359
+ var otaVersionUrl: String? { optionalStringValue(values, "otaVersionUrl") }
360
+ var appVersion: String? { optionalStringValue(values, "appVersion") }
361
+ var bluetoothName: String? { optionalStringValue(values, "bluetoothName") }
362
+ var serialNumber: String? { optionalStringValue(values, "serialNumber") }
363
+ var style: String? { optionalStringValue(values, "style") }
364
+ var color: String? { optionalStringValue(values, "color") }
365
+ var wifi: WifiStatus? {
366
+ if let wifi = values["wifi"] as? [String: Any] {
367
+ return WifiStatus(values: wifi)
368
+ }
369
+ if hasAnyKey(values, "wifiConnected", "wifiSsid", "wifiLocalIp") {
370
+ return WifiStatus.fromStoreValues(values)
371
+ }
372
+ return nil
373
+ }
374
+ var hotspot: HotspotStatus? {
375
+ if let hotspot = values["hotspot"] as? [String: Any] {
376
+ return HotspotStatus(values: hotspot)
377
+ }
378
+ if hasAnyKey(values, "hotspotEnabled", "hotspotSsid", "hotspotPassword", "hotspotGatewayIp") {
379
+ return HotspotStatus.fromStoreValues(values)
380
+ }
381
+ return nil
382
+ }
383
+ var dictionary: [String: Any] { GlassesStatus.updateDictionary(from: values) }
384
+ var batteryLevel: Int? { optionalIntValue(values, "batteryLevel") }
385
+ var charging: Bool? { optionalBoolValue(values, "charging") }
386
+ var caseBatteryLevel: Int? { optionalIntValue(values, "caseBatteryLevel") }
387
+ var caseCharging: Bool? { optionalBoolValue(values, "caseCharging") }
388
+ var caseOpen: Bool? { optionalBoolValue(values, "caseOpen") }
389
+ var caseRemoved: Bool? { optionalBoolValue(values, "caseRemoved") }
390
+ var headUp: Bool? { optionalBoolValue(values, "headUp") }
391
+ var controllerConnected: Bool? { optionalBoolValue(values, "controllerConnected") }
392
+ var controllerFullyBooted: Bool? { optionalBoolValue(values, "controllerFullyBooted") }
393
+ var controllerMacAddress: String? { optionalStringValue(values, "controllerMacAddress") }
394
+ var controllerBatteryLevel: Int? { optionalIntValue(values, "controllerBatteryLevel") }
395
+ var controllerSignalStrength: Int? { optionalIntValue(values, "controllerSignalStrength") }
396
+ var ringSignalStrength: Int? { optionalIntValue(values, "ringSignalStrength") }
397
+
398
+ var description: String {
399
+ values.description
400
+ }
401
+ }
402
+
403
+ struct BluetoothStatusUpdate: CustomStringConvertible {
404
+ let values: [String: Any]
405
+
406
+ init(values: [String: Any]) {
407
+ var normalizedValues = values
408
+ if let searchResults = values["searchResults"] as? [[String: Any]] {
409
+ normalizedValues["searchResults"] = searchResults.compactMap { Device(values: $0)?.dictionary }
410
+ }
411
+ if let galleryMode = optionalBoolValue(values, "gallery_mode") {
412
+ normalizedValues["galleryModeEnabled"] = galleryMode
413
+ normalizedValues.removeValue(forKey: "gallery_mode")
414
+ }
415
+ self.values = normalizedValues
416
+ }
417
+
418
+ var searching: Bool? { optionalBoolValue(values, "searching") }
419
+ var searchingController: Bool? { optionalBoolValue(values, "searchingController") }
420
+ var systemMicUnavailable: Bool? { optionalBoolValue(values, "systemMicUnavailable") }
421
+ var micEnabled: Bool? { optionalBoolValue(values, "micEnabled") }
422
+ var currentMic: String? { optionalStringValue(values, "currentMic") }
423
+ var micRanking: [String]? { optionalStringListValue(values, "micRanking") }
424
+ /// Nearby glasses in stable discovery order when included in an update. Existing entries keep their
425
+ /// array position as details refresh; new glasses append at the end, and removals should not reorder
426
+ /// remaining entries.
427
+ var searchResults: [Device]? {
428
+ optionalDictionaryListValue(values, "searchResults")?.compactMap(Device.init(values:))
429
+ }
430
+ var wifiScanResults: [WifiScanResult]? {
431
+ optionalDictionaryListValue(values, "wifiScanResults")?.map(WifiScanResult.init(values:))
432
+ }
433
+ var lastLog: [String]? { optionalStringListValue(values, "lastLog") }
434
+ var otherBtConnected: Bool? { optionalBoolValue(values, "otherBtConnected") }
435
+ var defaultWearable: String? { optionalStringValue(values, "default_wearable") }
436
+ var pendingWearable: String? { optionalStringValue(values, "pending_wearable") }
437
+ var deviceName: String? { optionalStringValue(values, "device_name") }
438
+ var deviceAddress: String? { optionalStringValue(values, "device_address") }
439
+ var defaultController: String? { optionalStringValue(values, "default_controller") }
440
+ var pendingController: String? { optionalStringValue(values, "pending_controller") }
441
+ var controllerDeviceName: String? { optionalStringValue(values, "controller_device_name") }
442
+ var screenDisabled: Bool? { optionalBoolValue(values, "screen_disabled") }
443
+ var preferredMic: String? { optionalStringValue(values, "preferred_mic") }
444
+ var sensingEnabled: Bool? { optionalBoolValue(values, "sensing_enabled") }
445
+ var powerSavingMode: Bool? { optionalBoolValue(values, "power_saving_mode") }
446
+ var brightness: Int? { optionalIntValue(values, "brightness") }
447
+ var autoBrightness: Bool? { optionalBoolValue(values, "auto_brightness") }
448
+ var dashboardHeight: Int? { optionalIntValue(values, "dashboard_height") }
449
+ var dashboardDepth: Int? { optionalIntValue(values, "dashboard_depth") }
450
+ var headUpAngle: Int? { optionalIntValue(values, "head_up_angle") }
451
+ var contextualDashboard: Bool? { optionalBoolValue(values, "contextual_dashboard") }
452
+ var galleryModeEnabled: Bool? { optionalBoolValue(values, "gallery_mode") ?? optionalBoolValue(values, "galleryModeEnabled") }
453
+ var buttonPhotoSize: ButtonPhotoSize? {
454
+ optionalStringValue(values, "button_photo_size").flatMap(ButtonPhotoSize.init(rawValue:))
455
+ }
456
+ var buttonCameraLed: Bool? { optionalBoolValue(values, "button_camera_led") }
457
+ var buttonMaxRecordingTime: Int? { optionalIntValue(values, "button_max_recording_time") }
458
+ var buttonVideoWidth: Int? { optionalIntValue(values, "button_video_width") }
459
+ var buttonVideoHeight: Int? { optionalIntValue(values, "button_video_height") }
460
+ var buttonVideoFrameRate: Int? { optionalIntValue(values, "button_video_fps") }
461
+ var shouldSendPcm: Bool? { optionalBoolValue(values, "should_send_pcm") }
462
+ var shouldSendLc3: Bool? { optionalBoolValue(values, "should_send_lc3") }
463
+ var shouldSendTranscript: Bool? { optionalBoolValue(values, "should_send_transcript") }
464
+ var offlineCaptionsRunning: Bool? { optionalBoolValue(values, "offline_captions_running") }
465
+ var localSttFallbackActive: Bool? { optionalBoolValue(values, "local_stt_fallback_active") }
466
+ var shouldSendBootingMessage: Bool? { optionalBoolValue(values, "shouldSendBootingMessage") }
467
+
468
+ var description: String {
469
+ values.description
470
+ }
471
+ }