@mentra/bluetooth-sdk 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/README.md +61 -42
  2. package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +20 -12
  3. package/android/src/main/java/com/mentra/bluetoothsdk/Bridge.kt +38 -12
  4. package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +2 -1
  5. package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +45 -51
  6. package/android/src/main/java/com/mentra/bluetoothsdk/audio/AudioModels.kt +125 -0
  7. package/android/src/main/java/com/mentra/bluetoothsdk/camera/CameraModels.kt +187 -0
  8. package/android/src/main/java/com/mentra/bluetoothsdk/connection/ConnectionModels.kt +78 -0
  9. package/android/src/main/java/com/mentra/bluetoothsdk/events/BluetoothEvents.kt +60 -0
  10. package/android/src/main/java/com/mentra/bluetoothsdk/internal/MapParsing.kt +132 -0
  11. package/android/src/main/java/com/mentra/bluetoothsdk/requests/DisplayRequests.kt +39 -0
  12. package/android/src/main/java/com/mentra/bluetoothsdk/status/DeviceStatus.kt +605 -0
  13. package/android/src/main/java/com/mentra/bluetoothsdk/status/RuntimeState.kt +199 -0
  14. package/android/src/main/java/com/mentra/bluetoothsdk/status/WifiHotspotStatus.kt +173 -0
  15. package/android/src/main/java/com/mentra/bluetoothsdk/streaming/StreamModels.kt +348 -0
  16. package/android/src/main/java/com/mentra/bluetoothsdk/types/DeviceModels.kt +75 -0
  17. package/build/BluetoothSdk.types.d.ts +24 -7
  18. package/build/BluetoothSdk.types.d.ts.map +1 -1
  19. package/build/BluetoothSdk.types.js.map +1 -1
  20. package/build/_internal.d.ts +1 -1
  21. package/build/_internal.js +1 -1
  22. package/build/_internal.js.map +1 -1
  23. package/build/_private/BluetoothSdkModule.d.ts +5 -4
  24. package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
  25. package/build/_private/BluetoothSdkModule.js +4 -1
  26. package/build/_private/BluetoothSdkModule.js.map +1 -1
  27. package/build/index.d.ts +4 -3
  28. package/build/index.d.ts.map +1 -1
  29. package/build/index.js +81 -2
  30. package/build/index.js.map +1 -1
  31. package/build/react/index.d.ts +2 -2
  32. package/build/react/index.d.ts.map +1 -1
  33. package/build/react/index.js +1 -2
  34. package/build/react/index.js.map +1 -1
  35. package/build/react/useBluetoothStatus.js +1 -1
  36. package/build/react/useBluetoothStatus.js.map +1 -1
  37. package/build/react/useGlassesConnection.d.ts.map +1 -1
  38. package/build/react/useGlassesConnection.js +13 -0
  39. package/build/react/useGlassesConnection.js.map +1 -1
  40. package/build/react/useMentraBluetooth.d.ts +98 -0
  41. package/build/react/useMentraBluetooth.d.ts.map +1 -0
  42. package/build/react/useMentraBluetooth.js +156 -0
  43. package/build/react/useMentraBluetooth.js.map +1 -0
  44. package/ios/BluetoothSdkModule.swift +9 -9
  45. package/ios/Source/Audio/AudioModels.swift +159 -0
  46. package/ios/Source/Bridge.swift +42 -8
  47. package/ios/Source/Camera/CameraModels.swift +246 -0
  48. package/ios/Source/Connection/ScanSession.swift +27 -0
  49. package/ios/Source/DeviceManager.swift +0 -1
  50. package/ios/Source/DeviceStore.swift +2 -1
  51. package/ios/Source/Errors/BluetoothError.swift +19 -0
  52. package/ios/Source/Events/BluetoothEvents.swift +115 -0
  53. package/ios/Source/Internal/BluetoothAvailability.swift +58 -0
  54. package/ios/Source/Internal/ValueParsing.swift +90 -0
  55. package/ios/Source/MentraBluetoothSDK.swift +32 -2040
  56. package/ios/Source/Requests/DisplayRequests.swift +58 -0
  57. package/ios/Source/Status/DeviceStatus.swift +463 -0
  58. package/ios/Source/Status/RuntimeState.swift +350 -0
  59. package/ios/Source/Status/WifiHotspotStatus.swift +326 -0
  60. package/ios/Source/Streaming/StreamModels.swift +436 -0
  61. package/ios/Source/Types/DeviceModels.swift +134 -0
  62. package/ios/Source/sgcs/G2.swift +97 -36
  63. package/package.json +1 -1
  64. package/src/BluetoothSdk.types.ts +24 -7
  65. package/src/_internal.ts +1 -1
  66. package/src/_private/BluetoothSdkModule.ts +10 -5
  67. package/src/index.ts +93 -4
  68. package/src/react/index.ts +13 -11
  69. package/src/react/useBluetoothStatus.ts +1 -1
  70. package/src/react/useGlassesConnection.ts +15 -0
  71. package/src/react/useMentraBluetooth.ts +307 -0
  72. package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothModels.kt +0 -1725
@@ -1,2015 +1,6 @@
1
1
  import CoreBluetooth
2
2
  import Foundation
3
3
 
4
- private func intValue(_ value: Any?) -> Int? {
5
- if let int = value as? Int { return int }
6
- if let double = value as? Double { return Int(double) }
7
- if let number = value as? NSNumber { return number.intValue }
8
- return nil
9
- }
10
-
11
- private func stringValue(_ values: [String: Any], _ keys: String...) -> String? {
12
- stringValue(values, keys)
13
- }
14
-
15
- private func stringValue(_ values: [String: Any], _ keys: [String]) -> String? {
16
- for key in keys {
17
- if let value = values[key] as? String {
18
- return value
19
- }
20
- }
21
- return nil
22
- }
23
-
24
- private func boolValue(_ values: [String: Any], _ keys: String...) -> Bool? {
25
- boolValue(values, keys)
26
- }
27
-
28
- private func boolValue(_ values: [String: Any], _ keys: [String]) -> Bool? {
29
- for key in keys {
30
- if let value = values[key] as? Bool { return value }
31
- if let value = values[key] as? NSNumber { return value.boolValue }
32
- }
33
- return nil
34
- }
35
-
36
- private func hasAnyKey(_ values: [String: Any], _ keys: String...) -> Bool {
37
- hasAnyKey(values, keys)
38
- }
39
-
40
- private func hasAnyKey(_ values: [String: Any], _ keys: [String]) -> Bool {
41
- keys.contains { values.keys.contains($0) }
42
- }
43
-
44
- private func optionalStringValue(_ values: [String: Any], _ keys: String...) -> String? {
45
- hasAnyKey(values, keys) ? (stringValue(values, keys) ?? "") : nil
46
- }
47
-
48
- private func nonEmptyStringValue(_ values: [String: Any], _ keys: String...) -> String? {
49
- for key in keys {
50
- guard let value = values[key] as? String else { continue }
51
- let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)
52
- if !trimmed.isEmpty {
53
- return value
54
- }
55
- }
56
- return nil
57
- }
58
-
59
- private func optionalIntValue(_ values: [String: Any], _ keys: String...) -> Int? {
60
- guard hasAnyKey(values, keys) else { return nil }
61
- for key in keys {
62
- if let value = intValue(values[key]) { return value }
63
- }
64
- return nil
65
- }
66
-
67
- private func optionalBoolValue(_ values: [String: Any], _ keys: String...) -> Bool? {
68
- hasAnyKey(values, keys) ? (boolValue(values, keys) ?? false) : nil
69
- }
70
-
71
- private func stringListValue(_ values: [String: Any], _ key: String) -> [String] {
72
- values[key] as? [String] ?? []
73
- }
74
-
75
- private func optionalStringListValue(_ values: [String: Any], _ key: String) -> [String]? {
76
- values.keys.contains(key) ? stringListValue(values, key) : nil
77
- }
78
-
79
- private func dictionaryListValue(_ values: [String: Any], _ key: String) -> [[String: Any]] {
80
- values[key] as? [[String: Any]] ?? []
81
- }
82
-
83
- private func optionalDictionaryListValue(_ values: [String: Any], _ key: String) -> [[String: Any]]? {
84
- values.keys.contains(key) ? dictionaryListValue(values, key) : nil
85
- }
86
-
87
- private func putIfNotNil(_ map: inout [String: Any], _ key: String, _ value: Any?) {
88
- if let value {
89
- map[key] = value
90
- }
91
- }
92
-
93
- public struct MentraBluetoothSDKConfiguration {
94
- public static let `default` = MentraBluetoothSDKConfiguration()
95
-
96
- public init() {}
97
- }
98
-
99
- public enum DeviceModel: String {
100
- case g1
101
- case g2
102
- case mentraLive
103
- case mentraNex
104
- case mach1
105
- case z100
106
- case frame
107
- case simulated
108
- case r1
109
-
110
- public var deviceType: String {
111
- switch self {
112
- case .g1:
113
- DeviceTypes.G1
114
- case .g2:
115
- DeviceTypes.G2
116
- case .mentraLive:
117
- DeviceTypes.LIVE
118
- case .mentraNex:
119
- DeviceTypes.NEX
120
- case .mach1:
121
- DeviceTypes.MACH1
122
- case .z100:
123
- DeviceTypes.Z100
124
- case .frame:
125
- DeviceTypes.FRAME
126
- case .simulated:
127
- DeviceTypes.SIMULATED
128
- case .r1:
129
- ControllerTypes.R1
130
- }
131
- }
132
-
133
- public static func fromDeviceType(_ deviceType: String?) -> DeviceModel {
134
- switch deviceType {
135
- case DeviceTypes.G1:
136
- .g1
137
- case DeviceTypes.G2:
138
- .g2
139
- case DeviceTypes.LIVE:
140
- .mentraLive
141
- case DeviceTypes.NEX:
142
- .mentraNex
143
- case DeviceTypes.MACH1:
144
- .mach1
145
- case DeviceTypes.Z100:
146
- .z100
147
- case DeviceTypes.FRAME:
148
- .frame
149
- case DeviceTypes.SIMULATED:
150
- .simulated
151
- case ControllerTypes.R1:
152
- .r1
153
- default:
154
- .mentraLive
155
- }
156
- }
157
- }
158
-
159
- public struct Device: Identifiable, Equatable, CustomStringConvertible {
160
- public let model: DeviceModel
161
- public let name: String
162
- public let identifier: String?
163
- public let rssi: Int?
164
- public let id: String
165
-
166
- public init(
167
- model: DeviceModel,
168
- name: String,
169
- identifier: String? = nil,
170
- rssi: Int? = nil,
171
- id: String? = nil
172
- ) {
173
- self.model = model
174
- self.name = name
175
- self.identifier = identifier
176
- self.rssi = rssi
177
- self.id = id ?? identifier.flatMap { $0.isEmpty ? nil : $0 } ?? "\(model.deviceType):\(name)"
178
- }
179
-
180
- public var description: String {
181
- "Device(model: \(model), name: \(name))"
182
- }
183
-
184
- var dictionary: [String: Any] {
185
- var values: [String: Any] = [
186
- "id": id,
187
- "model": model.deviceType,
188
- "name": name,
189
- ]
190
- if let identifier, !identifier.isEmpty {
191
- values["address"] = identifier
192
- }
193
- if let rssi {
194
- values["rssi"] = rssi
195
- }
196
- return values
197
- }
198
-
199
- init?(values: [String: Any]) {
200
- guard let model = stringValue(values, "model") else { return nil }
201
- guard let name = stringValue(values, "name") else { return nil }
202
- let identifier = stringValue(values, "address").flatMap { $0.isEmpty ? nil : $0 }
203
- let rssi = intValue(values["rssi"])
204
- self.init(
205
- model: DeviceModel.fromDeviceType(model),
206
- name: name,
207
- identifier: identifier,
208
- rssi: rssi,
209
- id: stringValue(values, "id")
210
- )
211
- }
212
- }
213
-
214
- public struct ConnectOptions {
215
- public let saveAsDefault: Bool
216
- public let cancelExistingConnectionAttempt: Bool
217
-
218
- public init(saveAsDefault: Bool = true, cancelExistingConnectionAttempt: Bool = true) {
219
- self.saveAsDefault = saveAsDefault
220
- self.cancelExistingConnectionAttempt = cancelExistingConnectionAttempt
221
- }
222
- }
223
-
224
- public struct WifiScanResult: CustomStringConvertible {
225
- public let ssid: String
226
- public let requiresPassword: Bool
227
- public let signalStrength: Int
228
- public let frequency: Int?
229
-
230
- public init(ssid: String, requiresPassword: Bool, signalStrength: Int, frequency: Int? = nil) {
231
- self.ssid = ssid
232
- self.requiresPassword = requiresPassword
233
- self.signalStrength = signalStrength
234
- self.frequency = frequency
235
- }
236
-
237
- init(values: [String: Any]) {
238
- ssid = stringValue(values, "ssid") ?? ""
239
- requiresPassword = boolValue(values, "requiresPassword") ?? false
240
- signalStrength = intValue(values["signalStrength"]) ?? -1
241
- frequency = intValue(values["frequency"])
242
- }
243
-
244
- var dictionary: [String: Any] {
245
- var values: [String: Any] = [
246
- "ssid": ssid,
247
- "requiresPassword": requiresPassword,
248
- "signalStrength": signalStrength,
249
- ]
250
- if let frequency {
251
- values["frequency"] = frequency
252
- }
253
- return values
254
- }
255
-
256
- public var description: String {
257
- "WifiScanResult(ssid: \(ssid), signalStrength: \(signalStrength))"
258
- }
259
- }
260
-
261
- public enum GlassesConnectionState: String, CustomStringConvertible, Equatable {
262
- case disconnected = "DISCONNECTED"
263
- case scanning = "SCANNING"
264
- case connecting = "CONNECTING"
265
- case bonding = "BONDING"
266
- case connected = "CONNECTED"
267
-
268
- public init(_ value: String?) {
269
- self = Self.fromValue(value) ?? .disconnected
270
- }
271
-
272
- public static func fromValue(_ value: String?) -> GlassesConnectionState? {
273
- guard let normalized = value?.trimmingCharacters(in: .whitespacesAndNewlines).uppercased(),
274
- !normalized.isEmpty
275
- else {
276
- return nil
277
- }
278
- return Self(rawValue: normalized)
279
- }
280
-
281
- public var isConnected: Bool {
282
- self == .connected
283
- }
284
-
285
- public var isBusy: Bool {
286
- self == .scanning || self == .connecting || self == .bonding
287
- }
288
-
289
- func statusValues(connected: Bool, fullyBooted: Bool) -> [String: Any] {
290
- if self == .connected || connected || fullyBooted {
291
- return ["state": "connected", "fullyBooted": fullyBooted]
292
- }
293
- switch self {
294
- case .scanning:
295
- return ["state": "scanning"]
296
- case .connecting:
297
- return ["state": "connecting"]
298
- case .bonding:
299
- return ["state": "bonding"]
300
- case .connected:
301
- return ["state": "connected", "fullyBooted": fullyBooted]
302
- case .disconnected:
303
- return ["state": "disconnected"]
304
- }
305
- }
306
-
307
- public var description: String {
308
- rawValue
309
- }
310
- }
311
-
312
- public struct GlassesStatus: CustomStringConvertible {
313
- let values: [String: Any]
314
-
315
- init(values: [String: Any]) {
316
- self.values = values
317
- }
318
-
319
- public func applying(_ update: GlassesStatusUpdate) -> GlassesStatus {
320
- GlassesStatus(values: values.merging(update.values) { _, new in new })
321
- }
322
-
323
- public func withBattery(level: Int, charging: Bool) -> GlassesStatus {
324
- applying(GlassesStatusUpdate(values: ["batteryLevel": level, "charging": charging]))
325
- }
326
-
327
- public func withWifi(_ wifi: WifiStatus) -> GlassesStatus {
328
- applying(GlassesStatusUpdate(values: wifi.storeValues))
329
- }
330
-
331
- public func withHotspot(_ hotspot: HotspotStatus) -> GlassesStatus {
332
- applying(GlassesStatusUpdate(values: hotspot.storeValues))
333
- }
334
-
335
- public func disconnected() -> GlassesStatus {
336
- applying(GlassesStatusUpdate(values: [
337
- "connected": false,
338
- "connectionState": "DISCONNECTED",
339
- "fullyBooted": false,
340
- "batteryLevel": -1,
341
- "charging": false,
342
- "hotspotEnabled": false,
343
- "hotspotGatewayIp": "",
344
- "hotspotPassword": "",
345
- "hotspotSsid": "",
346
- "wifiConnected": false,
347
- "wifiSsid": "",
348
- "wifiLocalIp": "",
349
- "signalStrength": -1,
350
- "signalStrengthUpdatedAt": 0,
351
- ]))
352
- }
353
-
354
- public var fullyBooted: Bool { boolValue(values, "fullyBooted") ?? false }
355
- public var connected: Bool { boolValue(values, "connected") ?? false }
356
- public var micEnabled: Bool { boolValue(values, "micEnabled") ?? false }
357
- public var connectionState: GlassesConnectionState { GlassesConnectionState(stringValue(values, "connectionState")) }
358
- public var bluetoothClassicConnected: Bool { boolValue(values, "bluetoothClassicConnected") ?? false }
359
- public var signalStrength: Int { intValue(values["signalStrength"]) ?? -1 }
360
- public var signalStrengthUpdatedAt: Int { intValue(values["signalStrengthUpdatedAt"]) ?? 0 }
361
- public var deviceModel: String { stringValue(values, "deviceModel") ?? "" }
362
- public var androidVersion: String { stringValue(values, "androidVersion") ?? "" }
363
- public var firmwareVersion: String { stringValue(values, "firmwareVersion") ?? "" }
364
- public var besFirmwareVersion: String { stringValue(values, "besFirmwareVersion") ?? "" }
365
- public var mtkFirmwareVersion: String { stringValue(values, "mtkFirmwareVersion") ?? "" }
366
- public var bluetoothMacAddress: String { stringValue(values, "bluetoothMacAddress") ?? "" }
367
- public var leftMacAddress: String { stringValue(values, "leftMacAddress") ?? "" }
368
- public var rightMacAddress: String { stringValue(values, "rightMacAddress") ?? "" }
369
- public var macAddress: String { stringValue(values, "macAddress") ?? "" }
370
- public var buildNumber: String { stringValue(values, "buildNumber") ?? "" }
371
- public var otaVersionUrl: String { stringValue(values, "otaVersionUrl") ?? "" }
372
- public var appVersion: String { stringValue(values, "appVersion") ?? "" }
373
- public var bluetoothName: String { stringValue(values, "bluetoothName") ?? "" }
374
- public var serialNumber: String { stringValue(values, "serialNumber") ?? "" }
375
- public var style: String { stringValue(values, "style") ?? "" }
376
- public var color: String { stringValue(values, "color") ?? "" }
377
- public var wifi: WifiStatus { WifiStatus.fromStoreValues(values) ?? .disconnected }
378
- public var hotspot: HotspotStatus { HotspotStatus.fromStoreValues(values) ?? .disabled }
379
- public var dictionary: [String: Any] { Self.dictionary(from: values) }
380
- public var batteryLevel: Int { intValue(values["batteryLevel"]) ?? -1 }
381
- public var charging: Bool { boolValue(values, "charging") ?? false }
382
- public var caseBatteryLevel: Int { intValue(values["caseBatteryLevel"]) ?? -1 }
383
- public var caseCharging: Bool { boolValue(values, "caseCharging") ?? false }
384
- public var caseOpen: Bool { boolValue(values, "caseOpen") ?? true }
385
- public var caseRemoved: Bool { boolValue(values, "caseRemoved") ?? true }
386
- public var headUp: Bool { boolValue(values, "headUp") ?? false }
387
- public var controllerConnected: Bool { boolValue(values, "controllerConnected") ?? false }
388
- public var controllerFullyBooted: Bool { boolValue(values, "controllerFullyBooted") ?? false }
389
- public var controllerMacAddress: String { stringValue(values, "controllerMacAddress") ?? "" }
390
- public var controllerBatteryLevel: Int { intValue(values["controllerBatteryLevel"]) ?? -1 }
391
- public var controllerSignalStrength: Int { intValue(values["controllerSignalStrength"]) ?? -1 }
392
- public var ringSignalStrength: Int { intValue(values["ringSignalStrength"]) ?? -1 }
393
-
394
- public var description: String {
395
- values.description
396
- }
397
-
398
- static func dictionary(from values: [String: Any]) -> [String: Any] {
399
- var dictionary = values
400
- dictionary["connection"] = GlassesConnectionState(stringValue(values, "connectionState")).statusValues(
401
- connected: boolValue(values, "connected") ?? false,
402
- fullyBooted: boolValue(values, "fullyBooted") ?? false
403
- )
404
- dictionary.removeValue(forKey: "connected")
405
- dictionary.removeValue(forKey: "fullyBooted")
406
- dictionary.removeValue(forKey: "connectionState")
407
- dictionary["wifi"] = (WifiStatus.fromStoreValues(values) ?? .disconnected).values
408
- dictionary["hotspot"] = (HotspotStatus.fromStoreValues(values) ?? .disabled).values
409
- dictionary.removeValue(forKey: "wifiConnected")
410
- dictionary.removeValue(forKey: "wifiSsid")
411
- dictionary.removeValue(forKey: "wifiLocalIp")
412
- dictionary.removeValue(forKey: "hotspotEnabled")
413
- dictionary.removeValue(forKey: "hotspotSsid")
414
- dictionary.removeValue(forKey: "hotspotPassword")
415
- dictionary.removeValue(forKey: "hotspotGatewayIp")
416
- return dictionary
417
- }
418
-
419
- static func updateDictionary(from values: [String: Any]) -> [String: Any] {
420
- var dictionary = values
421
- if hasAnyKey(values, "connection", "connected", "fullyBooted", "connectionState") {
422
- dictionary["connection"] = (values["connection"] as? [String: Any])
423
- ?? GlassesConnectionState(stringValue(values, "connectionState")).statusValues(
424
- connected: boolValue(values, "connected") ?? false,
425
- fullyBooted: boolValue(values, "fullyBooted") ?? false
426
- )
427
- dictionary.removeValue(forKey: "connected")
428
- dictionary.removeValue(forKey: "fullyBooted")
429
- dictionary.removeValue(forKey: "connectionState")
430
- }
431
- if hasAnyKey(values, "wifi", "wifiConnected", "wifiSsid", "wifiLocalIp") {
432
- let wifi = (values["wifi"] as? [String: Any]).flatMap(WifiStatus.init(values:))
433
- ?? WifiStatus.fromStoreValues(values)
434
- if let wifi {
435
- dictionary["wifi"] = wifi.values
436
- }
437
- dictionary.removeValue(forKey: "wifiConnected")
438
- dictionary.removeValue(forKey: "wifiSsid")
439
- dictionary.removeValue(forKey: "wifiLocalIp")
440
- }
441
- if hasAnyKey(values, "hotspot") {
442
- if let hotspot = (values["hotspot"] as? [String: Any]).flatMap(HotspotStatus.init(values:)) {
443
- dictionary["hotspot"] = hotspot.values
444
- }
445
- } else if hasAnyKey(values, "hotspotEnabled", "hotspotSsid", "hotspotPassword", "hotspotGatewayIp") {
446
- if let hotspot = HotspotStatus.fromStoreValues(values) {
447
- dictionary["hotspot"] = hotspot.values
448
- }
449
- dictionary.removeValue(forKey: "hotspotEnabled")
450
- dictionary.removeValue(forKey: "hotspotSsid")
451
- dictionary.removeValue(forKey: "hotspotPassword")
452
- dictionary.removeValue(forKey: "hotspotGatewayIp")
453
- }
454
- return dictionary
455
- }
456
- }
457
-
458
- public struct BluetoothStatus: CustomStringConvertible {
459
- let values: [String: Any]
460
-
461
- init(values: [String: Any]) {
462
- self.values = Self.normalized(values)
463
- }
464
-
465
- private static func normalized(_ values: [String: Any]) -> [String: Any] {
466
- var normalizedValues = values
467
- if let searchResults = values["searchResults"] as? [[String: Any]] {
468
- normalizedValues["searchResults"] = searchResults.compactMap { Device(values: $0)?.dictionary }
469
- }
470
- return normalizedValues
471
- }
472
-
473
- public func applying(_ update: BluetoothStatusUpdate) -> BluetoothStatus {
474
- BluetoothStatus(values: values.merging(update.values) { _, new in new })
475
- }
476
-
477
- public func withDefaultDevice(_ device: Device?) -> BluetoothStatus {
478
- applying(BluetoothStatusUpdate(values: [
479
- "default_wearable": device?.model.deviceType ?? "",
480
- "device_name": device?.name ?? "",
481
- "device_address": device?.identifier ?? "",
482
- ]))
483
- }
484
-
485
- public var searching: Bool { boolValue(values, "searching") ?? false }
486
- public var searchingController: Bool { boolValue(values, "searchingController") ?? false }
487
- public var systemMicUnavailable: Bool { boolValue(values, "systemMicUnavailable") ?? false }
488
- public var micEnabled: Bool { boolValue(values, "micEnabled") ?? false }
489
- public var currentMic: String { stringValue(values, "currentMic") ?? "" }
490
- public var micRanking: [String] { stringListValue(values, "micRanking") }
491
- /// Nearby glasses in stable discovery order. Existing entries keep their array position as
492
- /// details refresh; new glasses append at the end, and removals should not reorder remaining entries.
493
- public var searchResults: [Device] {
494
- dictionaryListValue(values, "searchResults").compactMap(Device.init(values:))
495
- }
496
- public var wifiScanResults: [WifiScanResult] {
497
- dictionaryListValue(values, "wifiScanResults").map(WifiScanResult.init(values:))
498
- }
499
- public var lastLog: [String] { stringListValue(values, "lastLog") }
500
- public var otherBtConnected: Bool { boolValue(values, "otherBtConnected") ?? false }
501
- public var defaultWearable: String { stringValue(values, "default_wearable") ?? "" }
502
- public var pendingWearable: String { stringValue(values, "pending_wearable") ?? "" }
503
- public var deviceName: String { stringValue(values, "device_name") ?? "" }
504
- public var deviceAddress: String { stringValue(values, "device_address") ?? "" }
505
- public var defaultController: String { stringValue(values, "default_controller") ?? "" }
506
- public var pendingController: String { stringValue(values, "pending_controller") ?? "" }
507
- public var controllerDeviceName: String { stringValue(values, "controller_device_name") ?? "" }
508
- public var screenDisabled: Bool { boolValue(values, "screen_disabled") ?? false }
509
- public var preferredMic: String { stringValue(values, "preferred_mic") ?? "auto" }
510
- public var sensingEnabled: Bool { boolValue(values, "sensing_enabled") ?? true }
511
- public var powerSavingMode: Bool { boolValue(values, "power_saving_mode") ?? false }
512
- public var brightness: Int { intValue(values["brightness"]) ?? 50 }
513
- public var autoBrightness: Bool { boolValue(values, "auto_brightness") ?? true }
514
- public var dashboardHeight: Int { intValue(values["dashboard_height"]) ?? 4 }
515
- public var dashboardDepth: Int { intValue(values["dashboard_depth"]) ?? 2 }
516
- public var headUpAngle: Int { intValue(values["head_up_angle"]) ?? 30 }
517
- public var contextualDashboard: Bool { boolValue(values, "contextual_dashboard") ?? true }
518
- public var galleryModeAuto: Bool { boolValue(values, "galleryModeAuto") ?? true }
519
- public var buttonPhotoSize: ButtonPhotoSize {
520
- ButtonPhotoSize(rawValue: stringValue(values, "button_photo_size") ?? "") ?? .medium
521
- }
522
- public var buttonCameraLed: Bool { boolValue(values, "button_camera_led") ?? true }
523
- public var buttonMaxRecordingTime: Int { intValue(values["button_max_recording_time"]) ?? 10 }
524
- public var buttonVideoWidth: Int { intValue(values["button_video_width"]) ?? 1280 }
525
- public var buttonVideoHeight: Int { intValue(values["button_video_height"]) ?? 720 }
526
- public var buttonVideoFrameRate: Int { intValue(values["button_video_fps"]) ?? 30 }
527
- public var shouldSendPcm: Bool { boolValue(values, "should_send_pcm") ?? false }
528
- public var shouldSendLc3: Bool { boolValue(values, "should_send_lc3") ?? false }
529
- public var shouldSendTranscript: Bool { boolValue(values, "should_send_transcript") ?? false }
530
- public var bypassVad: Bool { boolValue(values, "bypass_vad") ?? false }
531
- public var offlineCaptionsRunning: Bool { boolValue(values, "offline_captions_running") ?? false }
532
- public var localSttFallbackActive: Bool { boolValue(values, "local_stt_fallback_active") ?? false }
533
- public var shouldSendBootingMessage: Bool { boolValue(values, "shouldSendBootingMessage") ?? true }
534
-
535
- public var defaultDevice: Device? {
536
- guard !defaultWearable.isEmpty else { return nil }
537
- return Device(
538
- model: DeviceModel.fromDeviceType(defaultWearable),
539
- name: deviceName,
540
- identifier: deviceAddress.isEmpty ? nil : deviceAddress
541
- )
542
- }
543
-
544
- public var description: String {
545
- values.description
546
- }
547
- }
548
-
549
- public struct GlassesStatusUpdate: CustomStringConvertible {
550
- let values: [String: Any]
551
-
552
- init(values: [String: Any]) {
553
- self.values = values
554
- }
555
-
556
- public var fullyBooted: Bool? { optionalBoolValue(values, "fullyBooted") }
557
- public var connected: Bool? { optionalBoolValue(values, "connected") }
558
- public var micEnabled: Bool? { optionalBoolValue(values, "micEnabled") }
559
- public var connectionState: GlassesConnectionState? {
560
- GlassesConnectionState.fromValue(optionalStringValue(values, "connectionState"))
561
- }
562
- public var bluetoothClassicConnected: Bool? { optionalBoolValue(values, "bluetoothClassicConnected") }
563
- public var signalStrength: Int? { optionalIntValue(values, "signalStrength") }
564
- public var signalStrengthUpdatedAt: Int? { optionalIntValue(values, "signalStrengthUpdatedAt") }
565
- public var deviceModel: String? { optionalStringValue(values, "deviceModel") }
566
- public var androidVersion: String? { optionalStringValue(values, "androidVersion") }
567
- public var firmwareVersion: String? { optionalStringValue(values, "firmwareVersion") }
568
- public var besFirmwareVersion: String? { optionalStringValue(values, "besFirmwareVersion") }
569
- public var mtkFirmwareVersion: String? { optionalStringValue(values, "mtkFirmwareVersion") }
570
- public var bluetoothMacAddress: String? { optionalStringValue(values, "bluetoothMacAddress") }
571
- public var leftMacAddress: String? { optionalStringValue(values, "leftMacAddress") }
572
- public var rightMacAddress: String? { optionalStringValue(values, "rightMacAddress") }
573
- public var macAddress: String? { optionalStringValue(values, "macAddress") }
574
- public var buildNumber: String? { optionalStringValue(values, "buildNumber") }
575
- public var otaVersionUrl: String? { optionalStringValue(values, "otaVersionUrl") }
576
- public var appVersion: String? { optionalStringValue(values, "appVersion") }
577
- public var bluetoothName: String? { optionalStringValue(values, "bluetoothName") }
578
- public var serialNumber: String? { optionalStringValue(values, "serialNumber") }
579
- public var style: String? { optionalStringValue(values, "style") }
580
- public var color: String? { optionalStringValue(values, "color") }
581
- public var wifi: WifiStatus? {
582
- if let wifi = values["wifi"] as? [String: Any] {
583
- return WifiStatus(values: wifi)
584
- }
585
- if hasAnyKey(values, "wifiConnected", "wifiSsid", "wifiLocalIp") {
586
- return WifiStatus.fromStoreValues(values)
587
- }
588
- return nil
589
- }
590
- public var hotspot: HotspotStatus? {
591
- if let hotspot = values["hotspot"] as? [String: Any] {
592
- return HotspotStatus(values: hotspot)
593
- }
594
- if hasAnyKey(values, "hotspotEnabled", "hotspotSsid", "hotspotPassword", "hotspotGatewayIp") {
595
- return HotspotStatus.fromStoreValues(values)
596
- }
597
- return nil
598
- }
599
- public var dictionary: [String: Any] { GlassesStatus.updateDictionary(from: values) }
600
- public var batteryLevel: Int? { optionalIntValue(values, "batteryLevel") }
601
- public var charging: Bool? { optionalBoolValue(values, "charging") }
602
- public var caseBatteryLevel: Int? { optionalIntValue(values, "caseBatteryLevel") }
603
- public var caseCharging: Bool? { optionalBoolValue(values, "caseCharging") }
604
- public var caseOpen: Bool? { optionalBoolValue(values, "caseOpen") }
605
- public var caseRemoved: Bool? { optionalBoolValue(values, "caseRemoved") }
606
- public var headUp: Bool? { optionalBoolValue(values, "headUp") }
607
- public var controllerConnected: Bool? { optionalBoolValue(values, "controllerConnected") }
608
- public var controllerFullyBooted: Bool? { optionalBoolValue(values, "controllerFullyBooted") }
609
- public var controllerMacAddress: String? { optionalStringValue(values, "controllerMacAddress") }
610
- public var controllerBatteryLevel: Int? { optionalIntValue(values, "controllerBatteryLevel") }
611
- public var controllerSignalStrength: Int? { optionalIntValue(values, "controllerSignalStrength") }
612
- public var ringSignalStrength: Int? { optionalIntValue(values, "ringSignalStrength") }
613
-
614
- public var description: String {
615
- values.description
616
- }
617
- }
618
-
619
- public struct BluetoothStatusUpdate: CustomStringConvertible {
620
- let values: [String: Any]
621
-
622
- init(values: [String: Any]) {
623
- var normalizedValues = values
624
- if let searchResults = values["searchResults"] as? [[String: Any]] {
625
- normalizedValues["searchResults"] = searchResults.compactMap { Device(values: $0)?.dictionary }
626
- }
627
- self.values = normalizedValues
628
- }
629
-
630
- public var searching: Bool? { optionalBoolValue(values, "searching") }
631
- public var searchingController: Bool? { optionalBoolValue(values, "searchingController") }
632
- public var systemMicUnavailable: Bool? { optionalBoolValue(values, "systemMicUnavailable") }
633
- public var micEnabled: Bool? { optionalBoolValue(values, "micEnabled") }
634
- public var currentMic: String? { optionalStringValue(values, "currentMic") }
635
- public var micRanking: [String]? { optionalStringListValue(values, "micRanking") }
636
- /// Nearby glasses in stable discovery order when included in an update. Existing entries keep their
637
- /// array position as details refresh; new glasses append at the end, and removals should not reorder
638
- /// remaining entries.
639
- public var searchResults: [Device]? {
640
- optionalDictionaryListValue(values, "searchResults")?.compactMap(Device.init(values:))
641
- }
642
- public var wifiScanResults: [WifiScanResult]? {
643
- optionalDictionaryListValue(values, "wifiScanResults")?.map(WifiScanResult.init(values:))
644
- }
645
- public var lastLog: [String]? { optionalStringListValue(values, "lastLog") }
646
- public var otherBtConnected: Bool? { optionalBoolValue(values, "otherBtConnected") }
647
- public var defaultWearable: String? { optionalStringValue(values, "default_wearable") }
648
- public var pendingWearable: String? { optionalStringValue(values, "pending_wearable") }
649
- public var deviceName: String? { optionalStringValue(values, "device_name") }
650
- public var deviceAddress: String? { optionalStringValue(values, "device_address") }
651
- public var defaultController: String? { optionalStringValue(values, "default_controller") }
652
- public var pendingController: String? { optionalStringValue(values, "pending_controller") }
653
- public var controllerDeviceName: String? { optionalStringValue(values, "controller_device_name") }
654
- public var screenDisabled: Bool? { optionalBoolValue(values, "screen_disabled") }
655
- public var preferredMic: String? { optionalStringValue(values, "preferred_mic") }
656
- public var sensingEnabled: Bool? { optionalBoolValue(values, "sensing_enabled") }
657
- public var powerSavingMode: Bool? { optionalBoolValue(values, "power_saving_mode") }
658
- public var brightness: Int? { optionalIntValue(values, "brightness") }
659
- public var autoBrightness: Bool? { optionalBoolValue(values, "auto_brightness") }
660
- public var dashboardHeight: Int? { optionalIntValue(values, "dashboard_height") }
661
- public var dashboardDepth: Int? { optionalIntValue(values, "dashboard_depth") }
662
- public var headUpAngle: Int? { optionalIntValue(values, "head_up_angle") }
663
- public var contextualDashboard: Bool? { optionalBoolValue(values, "contextual_dashboard") }
664
- public var galleryModeAuto: Bool? { optionalBoolValue(values, "galleryModeAuto") }
665
- public var buttonPhotoSize: ButtonPhotoSize? {
666
- optionalStringValue(values, "button_photo_size").flatMap(ButtonPhotoSize.init(rawValue:))
667
- }
668
- public var buttonCameraLed: Bool? { optionalBoolValue(values, "button_camera_led") }
669
- public var buttonMaxRecordingTime: Int? { optionalIntValue(values, "button_max_recording_time") }
670
- public var buttonVideoWidth: Int? { optionalIntValue(values, "button_video_width") }
671
- public var buttonVideoHeight: Int? { optionalIntValue(values, "button_video_height") }
672
- public var buttonVideoFrameRate: Int? { optionalIntValue(values, "button_video_fps") }
673
- public var shouldSendPcm: Bool? { optionalBoolValue(values, "should_send_pcm") }
674
- public var shouldSendLc3: Bool? { optionalBoolValue(values, "should_send_lc3") }
675
- public var shouldSendTranscript: Bool? { optionalBoolValue(values, "should_send_transcript") }
676
- public var bypassVad: Bool? { optionalBoolValue(values, "bypass_vad") }
677
- public var offlineCaptionsRunning: Bool? { optionalBoolValue(values, "offline_captions_running") }
678
- public var localSttFallbackActive: Bool? { optionalBoolValue(values, "local_stt_fallback_active") }
679
- public var shouldSendBootingMessage: Bool? { optionalBoolValue(values, "shouldSendBootingMessage") }
680
-
681
- public var description: String {
682
- values.description
683
- }
684
- }
685
-
686
- public struct DisplayTextRequest {
687
- public let text: String
688
- public let x: Int
689
- public let y: Int
690
- public let size: Int
691
-
692
- public init(text: String, x: Int = 0, y: Int = 0, size: Int = 24) {
693
- self.text = text
694
- self.x = x
695
- self.y = y
696
- self.size = size
697
- }
698
-
699
- var dictionary: [String: Any] {
700
- [
701
- "text": text,
702
- "x": x,
703
- "y": y,
704
- "size": size,
705
- ]
706
- }
707
- }
708
-
709
- struct DisplayEventRequest {
710
- let values: [String: Any]
711
-
712
- init(values: [String: Any]) {
713
- self.values = values
714
- }
715
- }
716
-
717
- public struct DashboardPositionRequest {
718
- public let height: Int
719
- public let depth: Int
720
-
721
- public init(height: Int, depth: Int) {
722
- self.height = height
723
- self.depth = depth
724
- }
725
- }
726
-
727
- struct DashboardMenuItem {
728
- let title: String
729
- let packageName: String
730
- let values: [String: Any]
731
-
732
- init(title: String, packageName: String, values: [String: Any] = [:]) {
733
- self.title = title
734
- self.packageName = packageName
735
- self.values = values
736
- }
737
-
738
- var dictionary: [String: Any] {
739
- values.merging(["title": title, "packageName": packageName]) { _, new in new }
740
- }
741
- }
742
-
743
- public enum GalleryMode {
744
- case auto
745
- case manual
746
- }
747
-
748
- public enum PhotoSize: String {
749
- case small
750
- case medium
751
- case large
752
- case full
753
- }
754
-
755
- public enum ButtonPhotoSize: String {
756
- case small
757
- case medium
758
- case large
759
- }
760
-
761
- public enum PhotoCompression: String {
762
- case none
763
- case medium
764
- case heavy
765
- }
766
-
767
- public struct ButtonPhotoSettings {
768
- public let size: ButtonPhotoSize
769
-
770
- public init(size: ButtonPhotoSize) {
771
- self.size = size
772
- }
773
- }
774
-
775
- public struct ButtonVideoRecordingSettings {
776
- public let width: Int
777
- public let height: Int
778
- public let frameRate: Int
779
-
780
- public init(width: Int, height: Int, frameRate: Int) {
781
- self.width = width
782
- self.height = height
783
- self.frameRate = frameRate
784
- }
785
- }
786
-
787
- public enum CameraFov {
788
- case standard
789
- case wide
790
-
791
- var value: [String: Int] {
792
- switch self {
793
- case .standard:
794
- ["fov": 118, "roiPosition": 0]
795
- case .wide:
796
- ["fov": 118, "roiPosition": 0]
797
- }
798
- }
799
- }
800
-
801
- public enum MicPreference: String {
802
- case auto
803
- case phone
804
- case glasses
805
- case bluetooth
806
- }
807
-
808
- public struct PhotoRequest {
809
- public let requestId: String
810
- public let appId: String
811
- public let size: PhotoSize
812
- public let webhookUrl: String?
813
- public let authToken: String?
814
- public let compress: PhotoCompression?
815
- public let sound: Bool
816
-
817
- public init(
818
- requestId: String,
819
- appId: String,
820
- size: PhotoSize,
821
- webhookUrl: String? = nil,
822
- authToken: String? = nil,
823
- compress: PhotoCompression? = nil,
824
- sound: Bool
825
- ) {
826
- self.requestId = requestId
827
- self.appId = appId
828
- self.size = size
829
- self.webhookUrl = webhookUrl
830
- self.authToken = authToken
831
- self.compress = compress
832
- self.sound = sound
833
- }
834
- }
835
-
836
- public struct StreamVideoConfig {
837
- public let width: Int?
838
- public let height: Int?
839
- public let bitrate: Int?
840
- public let frameRate: Int?
841
-
842
- public init(
843
- width: Int? = nil,
844
- height: Int? = nil,
845
- bitrate: Int? = nil,
846
- frameRate: Int? = nil
847
- ) {
848
- self.width = width
849
- self.height = height
850
- self.bitrate = bitrate
851
- self.frameRate = frameRate
852
- }
853
-
854
- var dictionary: [String: Any] {
855
- var values: [String: Any] = [:]
856
- if let width { values["width"] = width }
857
- if let height { values["height"] = height }
858
- if let bitrate { values["bitrate"] = bitrate }
859
- if let frameRate { values["frameRate"] = frameRate }
860
- return values
861
- }
862
-
863
- init?(values: [String: Any]?) {
864
- guard let values else { return nil }
865
- self.init(
866
- width: intValue(values["width"]),
867
- height: intValue(values["height"]),
868
- bitrate: intValue(values["bitrate"]),
869
- frameRate: intValue(values["frameRate"])
870
- )
871
- }
872
- }
873
-
874
- public struct StreamAudioConfig {
875
- public let bitrate: Int?
876
- public let sampleRate: Int?
877
- public let echoCancellation: Bool?
878
- public let noiseSuppression: Bool?
879
-
880
- public init(
881
- bitrate: Int? = nil,
882
- sampleRate: Int? = nil,
883
- echoCancellation: Bool? = nil,
884
- noiseSuppression: Bool? = nil
885
- ) {
886
- self.bitrate = bitrate
887
- self.sampleRate = sampleRate
888
- self.echoCancellation = echoCancellation
889
- self.noiseSuppression = noiseSuppression
890
- }
891
-
892
- var dictionary: [String: Any] {
893
- var values: [String: Any] = [:]
894
- if let bitrate { values["bitrate"] = bitrate }
895
- if let sampleRate { values["sampleRate"] = sampleRate }
896
- if let echoCancellation { values["echoCancellation"] = echoCancellation }
897
- if let noiseSuppression { values["noiseSuppression"] = noiseSuppression }
898
- return values
899
- }
900
-
901
- init?(values: [String: Any]?) {
902
- guard let values else { return nil }
903
- self.init(
904
- bitrate: intValue(values["bitrate"]),
905
- sampleRate: intValue(values["sampleRate"]),
906
- echoCancellation: values["echoCancellation"] as? Bool,
907
- noiseSuppression: values["noiseSuppression"] as? Bool
908
- )
909
- }
910
- }
911
-
912
- public struct StreamRequest {
913
- public let streamUrl: String
914
- public let streamId: String
915
- public let keepAlive: Bool
916
- public let keepAliveIntervalSeconds: Int
917
- public let sound: Bool
918
- public let video: StreamVideoConfig?
919
- public let audio: StreamAudioConfig?
920
- public let extraValues: [String: Any]
921
-
922
- public init(
923
- streamUrl: String,
924
- streamId: String = "",
925
- keepAlive: Bool = true,
926
- keepAliveIntervalSeconds: Int = 15,
927
- sound: Bool = true,
928
- video: StreamVideoConfig? = nil,
929
- audio: StreamAudioConfig? = nil,
930
- extraValues: [String: Any] = [:]
931
- ) {
932
- self.streamUrl = streamUrl
933
- self.streamId = streamId
934
- self.keepAlive = keepAlive
935
- self.keepAliveIntervalSeconds = keepAliveIntervalSeconds
936
- self.sound = sound
937
- self.video = video
938
- self.audio = audio
939
- self.extraValues = extraValues
940
- }
941
-
942
- init(values: [String: Any]) {
943
- self.init(
944
- streamUrl: values["streamUrl"] as? String
945
- ?? values["rtmpUrl"] as? String
946
- ?? values["srtUrl"] as? String
947
- ?? values["whipUrl"] as? String
948
- ?? "",
949
- streamId: values["streamId"] as? String ?? "",
950
- keepAlive: values["keepAlive"] as? Bool ?? true,
951
- keepAliveIntervalSeconds: intValue(values["keepAliveIntervalSeconds"]) ?? 15,
952
- sound: values["sound"] as? Bool ?? true,
953
- video: StreamVideoConfig(values: values["video"] as? [String: Any]),
954
- audio: StreamAudioConfig(values: values["audio"] as? [String: Any]),
955
- extraValues: values
956
- )
957
- }
958
-
959
- public var values: [String: Any] {
960
- var values = extraValues
961
- values["type"] = "start_stream"
962
- values["streamUrl"] = streamUrl
963
- values["streamId"] = streamId
964
- values["keepAlive"] = keepAlive
965
- values["keepAliveIntervalSeconds"] = keepAliveIntervalSeconds
966
- // The camera light is a privacy indicator and cannot be disabled by SDK callers.
967
- values["flash"] = true
968
- values["sound"] = sound
969
- if let videoValues = video?.dictionary, !videoValues.isEmpty {
970
- values["video"] = videoValues
971
- }
972
- if let audioValues = audio?.dictionary, !audioValues.isEmpty {
973
- values["audio"] = audioValues
974
- }
975
- return values
976
- }
977
- }
978
-
979
- public struct StreamKeepAliveRequest {
980
- public let streamId: String
981
- public let ackId: String
982
- public let extraValues: [String: Any]
983
-
984
- public init(streamId: String, ackId: String, extraValues: [String: Any] = [:]) {
985
- self.streamId = streamId
986
- self.ackId = ackId
987
- self.extraValues = extraValues
988
- }
989
-
990
- init(values: [String: Any]) {
991
- self.init(
992
- streamId: values["streamId"] as? String ?? "",
993
- ackId: values["ackId"] as? String ?? "",
994
- extraValues: values
995
- )
996
- }
997
-
998
- public var values: [String: Any] {
999
- var values = extraValues
1000
- values["type"] = "keep_stream_alive"
1001
- values["streamId"] = streamId
1002
- values["ackId"] = ackId
1003
- return values
1004
- }
1005
- }
1006
-
1007
- public enum RgbLedAction: String {
1008
- case on
1009
- case off
1010
- }
1011
-
1012
- public enum RgbLedColor: String {
1013
- case red
1014
- case green
1015
- case blue
1016
- case orange
1017
- case white
1018
- }
1019
-
1020
- public struct RgbLedRequest {
1021
- public let requestId: String
1022
- public let packageName: String?
1023
- public let action: RgbLedAction
1024
- public let color: RgbLedColor?
1025
- public let onDurationMs: Int
1026
- public let offDurationMs: Int
1027
- public let count: Int
1028
-
1029
- public init(
1030
- requestId: String,
1031
- packageName: String?,
1032
- action: RgbLedAction,
1033
- color: RgbLedColor?,
1034
- onDurationMs: Int,
1035
- offDurationMs: Int,
1036
- count: Int
1037
- ) {
1038
- self.requestId = requestId
1039
- self.packageName = packageName
1040
- self.action = action
1041
- self.color = color
1042
- self.onDurationMs = onDurationMs
1043
- self.offDurationMs = offDurationMs
1044
- self.count = count
1045
- }
1046
- }
1047
-
1048
- public struct VideoRecordingRequest {
1049
- public let requestId: String
1050
- public let save: Bool
1051
- public let sound: Bool
1052
-
1053
- public init(requestId: String, save: Bool, sound: Bool) {
1054
- self.requestId = requestId
1055
- self.save = save
1056
- self.sound = sound
1057
- }
1058
- }
1059
-
1060
- public struct ButtonPressEvent: CustomStringConvertible {
1061
- public let buttonId: String
1062
- public let pressType: String
1063
- public let timestamp: Int?
1064
-
1065
- public init(buttonId: String, pressType: String, timestamp: Int? = nil) {
1066
- self.buttonId = buttonId
1067
- self.pressType = pressType
1068
- self.timestamp = timestamp
1069
- }
1070
-
1071
- public var description: String {
1072
- "ButtonPressEvent(buttonId: \(buttonId), pressType: \(pressType))"
1073
- }
1074
- }
1075
-
1076
- public struct TouchEvent: CustomStringConvertible {
1077
- public let values: [String: Any]
1078
-
1079
- public init(values: [String: Any]) {
1080
- self.values = values
1081
- }
1082
-
1083
- public var deviceModel: String? {
1084
- stringValue(values, "deviceModel")
1085
- }
1086
-
1087
- public var gestureName: String? {
1088
- stringValue(values, "gestureName")
1089
- }
1090
-
1091
- public var timestamp: Int? {
1092
- intValue(values["timestamp"])
1093
- }
1094
-
1095
- public var isSwipe: Bool {
1096
- gestureName?.localizedCaseInsensitiveContains("swipe") == true
1097
- }
1098
-
1099
- public var description: String {
1100
- "TouchEvent(gestureName: \(gestureName ?? "unknown"))"
1101
- }
1102
- }
1103
-
1104
- public enum WifiStatus: CustomStringConvertible, Equatable {
1105
- public enum State: String {
1106
- case disconnected
1107
- case connected
1108
- }
1109
-
1110
- case disconnected
1111
- case connected(ssid: String, localIp: String?)
1112
-
1113
- private init?(connected: Bool, ssid: String?, localIp: String?) {
1114
- if connected {
1115
- guard
1116
- let ssid = ssid?.trimmingCharacters(in: .whitespacesAndNewlines),
1117
- !ssid.isEmpty
1118
- else {
1119
- return nil
1120
- }
1121
- let trimmedLocalIp = localIp?.trimmingCharacters(in: .whitespacesAndNewlines)
1122
- self = .connected(
1123
- ssid: ssid,
1124
- localIp: trimmedLocalIp?.isEmpty == false ? trimmedLocalIp : nil
1125
- )
1126
- } else {
1127
- self = .disconnected
1128
- }
1129
- }
1130
-
1131
- init?(values: [String: Any]) {
1132
- if let nested = values["wifi"] as? [String: Any] {
1133
- guard let wifi = WifiStatus(values: nested) else {
1134
- return nil
1135
- }
1136
- self = wifi
1137
- return
1138
- }
1139
-
1140
- if let state = stringValue(values, "state")?.lowercased() {
1141
- switch state {
1142
- case State.connected.rawValue:
1143
- guard let wifi = WifiStatus(
1144
- connected: true,
1145
- ssid: nonEmptyStringValue(values, "ssid"),
1146
- localIp: nonEmptyStringValue(values, "localIp")
1147
- ) else {
1148
- return nil
1149
- }
1150
- self = wifi
1151
- case State.disconnected.rawValue:
1152
- self = .disconnected
1153
- default:
1154
- return nil
1155
- }
1156
- return
1157
- }
1158
-
1159
- return nil
1160
- }
1161
-
1162
- static func fromStoreValues(_ values: [String: Any]) -> WifiStatus? {
1163
- guard let connected = boolValue(values, "wifiConnected") else { return nil }
1164
- return fromStoreFields(
1165
- connected: connected,
1166
- ssid: nonEmptyStringValue(values, "wifiSsid"),
1167
- localIp: nonEmptyStringValue(values, "wifiLocalIp")
1168
- )
1169
- }
1170
-
1171
- static func fromStoreFields(connected: Bool, ssid: String?, localIp: String?) -> WifiStatus? {
1172
- WifiStatus(connected: connected, ssid: ssid, localIp: localIp)
1173
- }
1174
-
1175
- public var state: State {
1176
- switch self {
1177
- case .disconnected:
1178
- .disconnected
1179
- case .connected:
1180
- .connected
1181
- }
1182
- }
1183
-
1184
- public var isConnected: Bool {
1185
- if case .connected = self {
1186
- return true
1187
- }
1188
- return false
1189
- }
1190
-
1191
- public var values: [String: Any] {
1192
- switch self {
1193
- case .disconnected:
1194
- return ["state": State.disconnected.rawValue]
1195
- case let .connected(ssid, localIp):
1196
- var values: [String: Any] = [
1197
- "state": State.connected.rawValue,
1198
- "ssid": ssid,
1199
- ]
1200
- if let localIp = localIp {
1201
- values["localIp"] = localIp
1202
- }
1203
- return values
1204
- }
1205
- }
1206
-
1207
- var storeValues: [String: Any] {
1208
- switch self {
1209
- case .disconnected:
1210
- [
1211
- "wifiConnected": false,
1212
- "wifiSsid": "",
1213
- "wifiLocalIp": "",
1214
- ]
1215
- case let .connected(ssid, localIp):
1216
- [
1217
- "wifiConnected": true,
1218
- "wifiSsid": ssid,
1219
- "wifiLocalIp": localIp ?? "",
1220
- ]
1221
- }
1222
- }
1223
-
1224
- public var description: String {
1225
- switch self {
1226
- case .disconnected:
1227
- "WifiStatus(disconnected)"
1228
- case let .connected(ssid, localIp):
1229
- "WifiStatus(connected: \(ssid), localIp: \(localIp ?? "unknown"))"
1230
- }
1231
- }
1232
- }
1233
-
1234
- public struct WifiStatusEvent: CustomStringConvertible {
1235
- public let status: WifiStatus
1236
-
1237
- public init(status: WifiStatus) {
1238
- self.status = status
1239
- }
1240
-
1241
- init(connected: Bool, ssid: String?, localIp: String?) {
1242
- self.status = WifiStatus.fromStoreFields(connected: connected, ssid: ssid, localIp: localIp) ?? .disconnected
1243
- }
1244
-
1245
- init(values: [String: Any]) {
1246
- self.status = WifiStatus(values: values) ?? .disconnected
1247
- }
1248
-
1249
- public var values: [String: Any] {
1250
- status.values.merging(["type": "wifi_status_change"]) { _, new in new }
1251
- }
1252
-
1253
- public var description: String {
1254
- "WifiStatusEvent(\(status))"
1255
- }
1256
- }
1257
-
1258
- public enum HotspotStatus: CustomStringConvertible, Equatable {
1259
- public enum State: String {
1260
- case disabled
1261
- case enabled
1262
- }
1263
-
1264
- case disabled
1265
- case enabled(ssid: String, password: String, localIp: String)
1266
-
1267
- public init?(enabled: Bool, ssid: String?, password: String?, localIp: String?) {
1268
- if enabled {
1269
- guard
1270
- let ssid = ssid?.trimmingCharacters(in: .whitespacesAndNewlines),
1271
- !ssid.isEmpty,
1272
- let password = password?.trimmingCharacters(in: .whitespacesAndNewlines),
1273
- !password.isEmpty,
1274
- let localIp = localIp?.trimmingCharacters(in: .whitespacesAndNewlines),
1275
- !localIp.isEmpty
1276
- else {
1277
- return nil
1278
- }
1279
- self = .enabled(ssid: ssid, password: password, localIp: localIp)
1280
- } else {
1281
- self = .disabled
1282
- }
1283
- }
1284
-
1285
- public init?(values: [String: Any]) {
1286
- if let nested = values["hotspot"] as? [String: Any] {
1287
- self.init(values: nested)
1288
- return
1289
- }
1290
-
1291
- guard let state = stringValue(values, "state")?.lowercased() else {
1292
- return nil
1293
- }
1294
-
1295
- switch state {
1296
- case State.enabled.rawValue:
1297
- self.init(
1298
- enabled: true,
1299
- ssid: nonEmptyStringValue(values, "ssid"),
1300
- password: nonEmptyStringValue(values, "password"),
1301
- localIp: nonEmptyStringValue(values, "localIp")
1302
- )
1303
- case State.disabled.rawValue:
1304
- self = .disabled
1305
- default:
1306
- return nil
1307
- }
1308
- }
1309
-
1310
- static func fromStoreValues(_ values: [String: Any]) -> HotspotStatus? {
1311
- guard let enabled = boolValue(values, "hotspotEnabled") else {
1312
- return nil
1313
- }
1314
- return fromStoreFields(
1315
- enabled: enabled,
1316
- ssid: nonEmptyStringValue(values, "hotspotSsid"),
1317
- password: nonEmptyStringValue(values, "hotspotPassword"),
1318
- localIp: nonEmptyStringValue(values, "hotspotGatewayIp")
1319
- )
1320
- }
1321
-
1322
- static func fromStoreFields(enabled: Bool, ssid: String?, password: String?, localIp: String?) -> HotspotStatus? {
1323
- HotspotStatus(enabled: enabled, ssid: ssid, password: password, localIp: localIp)
1324
- }
1325
-
1326
- var storeValues: [String: Any] {
1327
- switch self {
1328
- case .disabled:
1329
- [
1330
- "hotspotEnabled": false,
1331
- "hotspotSsid": "",
1332
- "hotspotPassword": "",
1333
- "hotspotGatewayIp": "",
1334
- ]
1335
- case let .enabled(ssid, password, localIp):
1336
- [
1337
- "hotspotEnabled": true,
1338
- "hotspotSsid": ssid,
1339
- "hotspotPassword": password,
1340
- "hotspotGatewayIp": localIp,
1341
- ]
1342
- }
1343
- }
1344
-
1345
- public var values: [String: Any] {
1346
- switch self {
1347
- case .disabled:
1348
- ["state": State.disabled.rawValue]
1349
- case let .enabled(ssid, password, localIp):
1350
- [
1351
- "state": State.enabled.rawValue,
1352
- "ssid": ssid,
1353
- "password": password,
1354
- "localIp": localIp,
1355
- ]
1356
- }
1357
- }
1358
-
1359
- public var state: State {
1360
- switch self {
1361
- case .disabled:
1362
- .disabled
1363
- case .enabled:
1364
- .enabled
1365
- }
1366
- }
1367
-
1368
- public var isEnabled: Bool {
1369
- if case .enabled = self {
1370
- return true
1371
- }
1372
- return false
1373
- }
1374
-
1375
- public var description: String {
1376
- switch self {
1377
- case .disabled:
1378
- "HotspotStatus(disabled)"
1379
- case let .enabled(ssid, _, localIp):
1380
- "HotspotStatus(enabled: \(ssid), localIp: \(localIp))"
1381
- }
1382
- }
1383
- }
1384
-
1385
- public struct HotspotStatusEvent: CustomStringConvertible {
1386
- public let status: HotspotStatus
1387
-
1388
- public init(status: HotspotStatus) {
1389
- self.status = status
1390
- }
1391
-
1392
- init(enabled: Bool, ssid: String?, password: String?, localIp: String?) {
1393
- self.status = HotspotStatus.fromStoreFields(enabled: enabled, ssid: ssid, password: password, localIp: localIp) ?? .disabled
1394
- }
1395
-
1396
- init(values: [String: Any]) {
1397
- self.status = HotspotStatus(values: values) ?? .disabled
1398
- }
1399
-
1400
- public var values: [String: Any] {
1401
- status.values.merging(["type": "hotspot_status_change"]) { _, new in new }
1402
- }
1403
-
1404
- public var description: String {
1405
- "HotspotStatusEvent(\(status))"
1406
- }
1407
- }
1408
-
1409
- public struct HotspotErrorEvent: CustomStringConvertible {
1410
- public let values: [String: Any]
1411
-
1412
- public init(values: [String: Any]) {
1413
- self.values = values
1414
- }
1415
-
1416
- public var message: String? {
1417
- stringValue(values, "errorMessage")
1418
- }
1419
-
1420
- public var timestamp: Int? {
1421
- intValue(values["timestamp"])
1422
- }
1423
-
1424
- public var description: String {
1425
- "HotspotErrorEvent(message: \(message ?? "unknown"))"
1426
- }
1427
- }
1428
-
1429
- public enum PhotoResponse: CustomStringConvertible, Equatable {
1430
- public enum State: String {
1431
- case success
1432
- case error
1433
- }
1434
-
1435
- case success(requestId: String, uploadUrl: String, timestamp: Int)
1436
- case error(requestId: String, errorCode: String?, errorMessage: String, timestamp: Int)
1437
-
1438
- public init(values: [String: Any]) {
1439
- let requestId = stringValue(values, "requestId") ?? ""
1440
- let timestamp = intValue(values["timestamp"]) ?? Int(Date().timeIntervalSince1970 * 1000)
1441
- let state = stringValue(values, "state")?.lowercased()
1442
- if state == State.success.rawValue {
1443
- self = .success(
1444
- requestId: requestId,
1445
- uploadUrl: stringValue(values, "uploadUrl") ?? "",
1446
- timestamp: timestamp
1447
- )
1448
- } else {
1449
- self = .error(
1450
- requestId: requestId,
1451
- errorCode: stringValue(values, "errorCode"),
1452
- errorMessage: stringValue(values, "errorMessage") ?? "Unknown photo error",
1453
- timestamp: timestamp
1454
- )
1455
- }
1456
- }
1457
-
1458
- public var state: State {
1459
- switch self {
1460
- case .success:
1461
- .success
1462
- case .error:
1463
- .error
1464
- }
1465
- }
1466
-
1467
- public var requestId: String {
1468
- switch self {
1469
- case let .success(requestId, _, _), let .error(requestId, _, _, _):
1470
- requestId
1471
- }
1472
- }
1473
-
1474
- public var timestamp: Int {
1475
- switch self {
1476
- case let .success(_, _, timestamp), let .error(_, _, _, timestamp):
1477
- timestamp
1478
- }
1479
- }
1480
-
1481
- public var values: [String: Any] {
1482
- switch self {
1483
- case let .success(requestId, uploadUrl, timestamp):
1484
- return [
1485
- "state": State.success.rawValue,
1486
- "requestId": requestId,
1487
- "uploadUrl": uploadUrl,
1488
- "timestamp": timestamp,
1489
- ]
1490
- case let .error(requestId, errorCode, errorMessage, timestamp):
1491
- var values: [String: Any] = [
1492
- "state": State.error.rawValue,
1493
- "requestId": requestId,
1494
- "errorMessage": errorMessage,
1495
- "timestamp": timestamp,
1496
- ]
1497
- if let errorCode, !errorCode.isEmpty {
1498
- values["errorCode"] = errorCode
1499
- }
1500
- return values
1501
- }
1502
- }
1503
-
1504
- public var description: String {
1505
- "PhotoResponse(requestId: \(requestId), state: \(state.rawValue))"
1506
- }
1507
- }
1508
-
1509
- public struct PhotoResponseEvent: CustomStringConvertible {
1510
- public let response: PhotoResponse
1511
-
1512
- public init(response: PhotoResponse) {
1513
- self.response = response
1514
- }
1515
-
1516
- public init(values: [String: Any]) {
1517
- self.response = PhotoResponse(values: values)
1518
- }
1519
-
1520
- public var requestId: String {
1521
- response.requestId
1522
- }
1523
-
1524
- public var values: [String: Any] {
1525
- var values = response.values
1526
- values["type"] = "photo_response"
1527
- return values
1528
- }
1529
-
1530
- public var description: String {
1531
- "PhotoResponseEvent(requestId: \(requestId), state: \(response.state.rawValue))"
1532
- }
1533
- }
1534
-
1535
- public enum StreamState: String, Equatable {
1536
- case initializing
1537
- case streaming
1538
- case stopping
1539
- case stopped
1540
- case reconnecting
1541
- case reconnected
1542
- case reconnectFailed = "reconnect_failed"
1543
- case error
1544
-
1545
- fileprivate static func from(_ value: String?) -> StreamState? {
1546
- switch value?.lowercased() {
1547
- case "initializing", "starting", "connecting":
1548
- return .initializing
1549
- case "streaming", "streaming_started", "active":
1550
- return .streaming
1551
- case "stopping":
1552
- return .stopping
1553
- case "stopped", "not_streaming", "disconnected", "timeout":
1554
- return .stopped
1555
- case "reconnecting":
1556
- return .reconnecting
1557
- case "reconnected":
1558
- return .reconnected
1559
- case "reconnect_failed":
1560
- return .reconnectFailed
1561
- case "error", "error_not_streaming":
1562
- return .error
1563
- default:
1564
- return nil
1565
- }
1566
- }
1567
- }
1568
-
1569
- public enum StreamStatusKind: String, Equatable {
1570
- case lifecycle
1571
- case reconnect
1572
- case error
1573
- case snapshot
1574
- }
1575
-
1576
- public enum StreamStatus: CustomStringConvertible, Equatable {
1577
- case lifecycle(state: StreamState, streamId: String?, timestamp: Int?)
1578
- case reconnecting(streamId: String?, attempt: Int, maxAttempts: Int, reason: String, timestamp: Int?)
1579
- case reconnected(streamId: String?, attempt: Int, timestamp: Int?)
1580
- case reconnectFailed(streamId: String?, maxAttempts: Int, timestamp: Int?)
1581
- case error(streamId: String?, errorDetails: String, timestamp: Int?)
1582
- case snapshot(state: StreamState, streaming: Bool, reconnecting: Bool, streamId: String?, attempt: Int?, timestamp: Int?)
1583
-
1584
- public init(values: [String: Any]) {
1585
- let rawState = stringValue(values, "status")
1586
- let streamId = stringValue(values, "streamId")
1587
- let timestamp = intValue(values["timestamp"])
1588
- let attempt = optionalIntValue(values, "attempt")
1589
- let maxAttempts = optionalIntValue(values, "maxAttempts") ?? 0
1590
-
1591
- if hasAnyKey(values, "streaming") || hasAnyKey(values, "reconnecting") {
1592
- let streaming = boolValue(values, "streaming") == true
1593
- let reconnecting = boolValue(values, "reconnecting") == true
1594
- let snapshotState: StreamState = reconnecting ? .reconnecting : (streaming ? .streaming : .stopped)
1595
- self = .snapshot(
1596
- state: snapshotState,
1597
- streaming: streaming,
1598
- reconnecting: reconnecting,
1599
- streamId: streamId,
1600
- attempt: attempt,
1601
- timestamp: timestamp
1602
- )
1603
- return
1604
- }
1605
-
1606
- guard let state = StreamState.from(rawState) else {
1607
- self = .error(
1608
- streamId: streamId,
1609
- errorDetails: rawState.map { "Unknown stream status: \($0)" } ?? "Missing stream status",
1610
- timestamp: timestamp
1611
- )
1612
- return
1613
- }
1614
-
1615
- switch state {
1616
- case .reconnecting:
1617
- self = .reconnecting(
1618
- streamId: streamId,
1619
- attempt: attempt ?? 0,
1620
- maxAttempts: maxAttempts,
1621
- reason: stringValue(values, "reason") ?? "",
1622
- timestamp: timestamp
1623
- )
1624
- case .reconnected:
1625
- self = .reconnected(streamId: streamId, attempt: attempt ?? 0, timestamp: timestamp)
1626
- case .reconnectFailed:
1627
- self = .reconnectFailed(streamId: streamId, maxAttempts: maxAttempts, timestamp: timestamp)
1628
- case .error:
1629
- self = .error(
1630
- streamId: streamId,
1631
- errorDetails: stringValue(values, "errorDetails")
1632
- ?? (rawState == "error_not_streaming" ? "not_streaming" : "Unknown stream error"),
1633
- timestamp: timestamp
1634
- )
1635
- default:
1636
- self = .lifecycle(state: state, streamId: streamId, timestamp: timestamp)
1637
- }
1638
- }
1639
-
1640
- public var kind: StreamStatusKind {
1641
- switch self {
1642
- case .lifecycle:
1643
- .lifecycle
1644
- case .reconnecting, .reconnected, .reconnectFailed:
1645
- .reconnect
1646
- case .error:
1647
- .error
1648
- case .snapshot:
1649
- .snapshot
1650
- }
1651
- }
1652
-
1653
- public var state: StreamState {
1654
- switch self {
1655
- case let .lifecycle(state, _, _):
1656
- state
1657
- case .reconnecting:
1658
- .reconnecting
1659
- case .reconnected:
1660
- .reconnected
1661
- case .reconnectFailed:
1662
- .reconnectFailed
1663
- case .error:
1664
- .error
1665
- case let .snapshot(state, _, _, _, _, _):
1666
- state
1667
- }
1668
- }
1669
-
1670
- public var streamId: String? {
1671
- switch self {
1672
- case let .lifecycle(_, streamId, _),
1673
- let .reconnecting(streamId, _, _, _, _),
1674
- let .reconnected(streamId, _, _),
1675
- let .reconnectFailed(streamId, _, _),
1676
- let .error(streamId, _, _),
1677
- let .snapshot(_, _, _, streamId, _, _):
1678
- streamId
1679
- }
1680
- }
1681
-
1682
- public var timestamp: Int? {
1683
- switch self {
1684
- case let .lifecycle(_, _, timestamp),
1685
- let .reconnecting(_, _, _, _, timestamp),
1686
- let .reconnected(_, _, timestamp),
1687
- let .reconnectFailed(_, _, timestamp),
1688
- let .error(_, _, timestamp),
1689
- let .snapshot(_, _, _, _, _, timestamp):
1690
- timestamp
1691
- }
1692
- }
1693
-
1694
- public var values: [String: Any] {
1695
- var values: [String: Any] = [
1696
- "kind": kind.rawValue,
1697
- "status": state.rawValue,
1698
- ]
1699
- if let streamId, !streamId.isEmpty {
1700
- values["streamId"] = streamId
1701
- }
1702
- if let timestamp {
1703
- values["timestamp"] = timestamp
1704
- }
1705
-
1706
- switch self {
1707
- case .lifecycle:
1708
- break
1709
- case let .reconnecting(_, attempt, maxAttempts, reason, _):
1710
- values["attempt"] = attempt
1711
- values["maxAttempts"] = maxAttempts
1712
- values["reason"] = reason
1713
- case let .reconnected(_, attempt, _):
1714
- values["attempt"] = attempt
1715
- case let .reconnectFailed(_, maxAttempts, _):
1716
- values["maxAttempts"] = maxAttempts
1717
- case let .error(_, errorDetails, _):
1718
- values["errorDetails"] = errorDetails
1719
- case let .snapshot(_, streaming, reconnecting, _, attempt, _):
1720
- values["streaming"] = streaming
1721
- values["reconnecting"] = reconnecting
1722
- if let attempt {
1723
- values["attempt"] = attempt
1724
- }
1725
- }
1726
-
1727
- return values
1728
- }
1729
-
1730
- public var description: String {
1731
- "StreamStatus(kind: \(kind.rawValue), status: \(state.rawValue), streamId: \(streamId ?? "none"))"
1732
- }
1733
- }
1734
-
1735
- public struct StreamStatusEvent: CustomStringConvertible {
1736
- public let status: StreamStatus
1737
-
1738
- public init(status: StreamStatus) {
1739
- self.status = status
1740
- }
1741
-
1742
- public init(values: [String: Any]) {
1743
- self.status = StreamStatus(values: values)
1744
- }
1745
-
1746
- public var state: StreamState {
1747
- status.state
1748
- }
1749
-
1750
- public var streamId: String? {
1751
- status.streamId
1752
- }
1753
-
1754
- public var values: [String: Any] {
1755
- var values = status.values
1756
- values["type"] = "stream_status"
1757
- return values
1758
- }
1759
-
1760
- public var description: String {
1761
- "StreamStatusEvent(kind: \(status.kind.rawValue), status: \(state.rawValue), streamId: \(streamId ?? "none"))"
1762
- }
1763
- }
1764
-
1765
- public struct KeepAliveAckEvent: CustomStringConvertible, Equatable {
1766
- public let streamId: String
1767
- public let ackId: String
1768
- public let timestamp: Int?
1769
-
1770
- public init(streamId: String, ackId: String, timestamp: Int? = nil) {
1771
- self.streamId = streamId
1772
- self.ackId = ackId
1773
- self.timestamp = timestamp
1774
- }
1775
-
1776
- public init(values: [String: Any]) {
1777
- self.streamId = stringValue(values, "streamId") ?? ""
1778
- self.ackId = stringValue(values, "ackId") ?? ""
1779
- self.timestamp = intValue(values["timestamp"])
1780
- }
1781
-
1782
- public var values: [String: Any] {
1783
- var values: [String: Any] = [
1784
- "type": "keep_alive_ack",
1785
- "streamId": streamId,
1786
- "ackId": ackId,
1787
- ]
1788
- if let timestamp {
1789
- values["timestamp"] = timestamp
1790
- }
1791
- return values
1792
- }
1793
-
1794
- public var description: String {
1795
- "KeepAliveAckEvent(streamId: \(streamId), ackId: \(ackId))"
1796
- }
1797
- }
1798
-
1799
- public struct BluetoothError: Error, LocalizedError, CustomStringConvertible {
1800
- public let code: String
1801
- public let message: String
1802
-
1803
- public init(code: String, message: String) {
1804
- self.code = code
1805
- self.message = message
1806
- }
1807
-
1808
- public var errorDescription: String? {
1809
- message
1810
- }
1811
-
1812
- public var description: String {
1813
- "\(code): \(message)"
1814
- }
1815
- }
1816
-
1817
- private final class BluetoothAvailability: NSObject, CBCentralManagerDelegate {
1818
- static let shared = BluetoothAvailability()
1819
-
1820
- private var centralManager: CBCentralManager?
1821
- private var state: CBManagerState = .unknown
1822
-
1823
- override private init() {
1824
- super.init()
1825
- centralManager = CBCentralManager(
1826
- delegate: self,
1827
- queue: .main,
1828
- options: [CBCentralManagerOptionShowPowerAlertKey: false]
1829
- )
1830
- state = centralManager?.state ?? .unknown
1831
- }
1832
-
1833
- func centralManagerDidUpdateState(_ central: CBCentralManager) {
1834
- state = central.state
1835
- }
1836
-
1837
- func requirePoweredOn(operation: String) throws {
1838
- if let current = centralManager?.state {
1839
- state = current
1840
- }
1841
- switch state {
1842
- case .poweredOn:
1843
- return
1844
- case .poweredOff:
1845
- throw BluetoothError(
1846
- code: "bluetooth_powered_off",
1847
- message: "Turn on phone Bluetooth to \(operation)."
1848
- )
1849
- case .unauthorized:
1850
- throw BluetoothError(
1851
- code: "bluetooth_unauthorized",
1852
- message: "Allow Bluetooth access to \(operation)."
1853
- )
1854
- case .unsupported:
1855
- throw BluetoothError(
1856
- code: "bluetooth_unsupported",
1857
- message: "This phone does not support Bluetooth."
1858
- )
1859
- case .resetting, .unknown:
1860
- throw BluetoothError(
1861
- code: "bluetooth_not_ready",
1862
- message: "Bluetooth is not ready yet. Try again."
1863
- )
1864
- @unknown default:
1865
- throw BluetoothError(
1866
- code: "bluetooth_unavailable",
1867
- message: "Bluetooth is unavailable. Try again."
1868
- )
1869
- }
1870
- }
1871
- }
1872
-
1873
- public enum ScanStopReason {
1874
- case completed
1875
- case cancelled
1876
- case error
1877
- }
1878
-
1879
- @MainActor
1880
- public final class ScanSession {
1881
- private let stopAction: () -> Void
1882
- private var stopped = false
1883
-
1884
- init(stopAction: @escaping () -> Void) {
1885
- self.stopAction = stopAction
1886
- }
1887
-
1888
- public func stop() {
1889
- guard !stopped else { return }
1890
- stopped = true
1891
- stopAction()
1892
- }
1893
-
1894
- fileprivate func markStopped() {
1895
- stopped = true
1896
- }
1897
- }
1898
-
1899
- public struct LocalTranscriptionEvent: CustomStringConvertible {
1900
- public let text: String
1901
- public let isFinal: Bool
1902
- public let values: [String: Any]
1903
-
1904
- public init(text: String, isFinal: Bool, values: [String: Any]) {
1905
- self.text = text
1906
- self.isFinal = isFinal
1907
- self.values = values
1908
- }
1909
-
1910
- public var description: String {
1911
- "LocalTranscriptionEvent(text: \(text), isFinal: \(isFinal))"
1912
- }
1913
- }
1914
-
1915
- public struct GlassesMediaVolumeGetResult: CustomStringConvertible {
1916
- public let level: Int?
1917
- public let statusCode: Int?
1918
- public let values: [String: Any]
1919
-
1920
- public init(values: [String: Any]) {
1921
- self.level = intValue(values["level"])
1922
- self.statusCode = intValue(values["statusCode"])
1923
- self.values = values
1924
- }
1925
-
1926
- public var description: String {
1927
- let levelText = level.map(String.init) ?? "unknown"
1928
- let statusCodeText = statusCode.map(String.init) ?? "unknown"
1929
- return "GlassesMediaVolumeGetResult(level: \(levelText), statusCode: \(statusCodeText))"
1930
- }
1931
- }
1932
-
1933
- public struct GlassesMediaVolumeSetResult: CustomStringConvertible {
1934
- public let statusCode: Int?
1935
- public let values: [String: Any]
1936
-
1937
- public init(values: [String: Any]) {
1938
- self.statusCode = intValue(values["statusCode"])
1939
- self.values = values
1940
- }
1941
-
1942
- public var description: String {
1943
- "GlassesMediaVolumeSetResult(statusCode: \(statusCode.map(String.init) ?? "unknown"))"
1944
- }
1945
- }
1946
-
1947
- public enum BluetoothEvent: CustomStringConvertible {
1948
- case buttonPress(ButtonPressEvent)
1949
- case touch(TouchEvent)
1950
- case wifiStatus(WifiStatusEvent)
1951
- case hotspotStatus(HotspotStatusEvent)
1952
- case hotspotError(HotspotErrorEvent)
1953
- case photoResponse(PhotoResponseEvent)
1954
- case streamStatus(StreamStatusEvent)
1955
- case keepAliveAck(KeepAliveAckEvent)
1956
- case localTranscription(LocalTranscriptionEvent)
1957
- case raw(name: String, values: [String: Any])
1958
-
1959
- public var description: String {
1960
- switch self {
1961
- case let .buttonPress(event):
1962
- event.description
1963
- case let .touch(event):
1964
- event.description
1965
- case let .wifiStatus(event):
1966
- event.description
1967
- case let .hotspotStatus(event):
1968
- event.description
1969
- case let .hotspotError(event):
1970
- event.description
1971
- case let .photoResponse(event):
1972
- event.description
1973
- case let .streamStatus(event):
1974
- event.description
1975
- case let .keepAliveAck(event):
1976
- event.description
1977
- case let .localTranscription(event):
1978
- event.description
1979
- case let .raw(name, values):
1980
- "\(name): \(values)"
1981
- }
1982
- }
1983
- }
1984
-
1985
- @MainActor
1986
- public protocol MentraBluetoothSDKDelegate: AnyObject {
1987
- func mentraBluetoothSDK(_ sdk: MentraBluetoothSDK, didUpdateGlassesStatus status: GlassesStatusUpdate)
1988
- func mentraBluetoothSDK(_ sdk: MentraBluetoothSDK, didUpdateBluetoothStatus status: BluetoothStatusUpdate)
1989
- func mentraBluetoothSDK(_ sdk: MentraBluetoothSDK, didDiscover device: Device)
1990
- func mentraBluetoothSDK(_ sdk: MentraBluetoothSDK, didStopScan reason: ScanStopReason)
1991
- func mentraBluetoothSDK(_ sdk: MentraBluetoothSDK, didReceive event: BluetoothEvent)
1992
- func mentraBluetoothSDK(_ sdk: MentraBluetoothSDK, didReceiveMicPcm frame: Data)
1993
- func mentraBluetoothSDK(_ sdk: MentraBluetoothSDK, didReceiveMicLc3 frame: Data)
1994
- func mentraBluetoothSDK(_ sdk: MentraBluetoothSDK, didChangeDefaultDevice device: Device?)
1995
- func mentraBluetoothSDK(_ sdk: MentraBluetoothSDK, didLog message: String)
1996
- func mentraBluetoothSDK(_ sdk: MentraBluetoothSDK, didFail error: BluetoothError)
1997
- }
1998
-
1999
- @MainActor
2000
- public extension MentraBluetoothSDKDelegate {
2001
- func mentraBluetoothSDK(_: MentraBluetoothSDK, didUpdateGlassesStatus _: GlassesStatusUpdate) {}
2002
- func mentraBluetoothSDK(_: MentraBluetoothSDK, didUpdateBluetoothStatus _: BluetoothStatusUpdate) {}
2003
- func mentraBluetoothSDK(_: MentraBluetoothSDK, didDiscover _: Device) {}
2004
- func mentraBluetoothSDK(_: MentraBluetoothSDK, didStopScan _: ScanStopReason) {}
2005
- func mentraBluetoothSDK(_: MentraBluetoothSDK, didReceive _: BluetoothEvent) {}
2006
- func mentraBluetoothSDK(_: MentraBluetoothSDK, didReceiveMicPcm _: Data) {}
2007
- func mentraBluetoothSDK(_: MentraBluetoothSDK, didReceiveMicLc3 _: Data) {}
2008
- func mentraBluetoothSDK(_: MentraBluetoothSDK, didChangeDefaultDevice _: Device?) {}
2009
- func mentraBluetoothSDK(_: MentraBluetoothSDK, didLog _: String) {}
2010
- func mentraBluetoothSDK(_: MentraBluetoothSDK, didFail _: BluetoothError) {}
2011
- }
2012
-
2013
4
  @MainActor
2014
5
  private final class ActiveScanSession {
2015
6
  let model: DeviceModel
@@ -2058,11 +49,27 @@ public final class MentraBluetoothSDK {
2058
49
  }
2059
50
  }
2060
51
 
2061
- public var glassesStatus: GlassesStatus {
52
+ public var state: MentraBluetoothState {
53
+ MentraBluetoothState(glassesStatus: glassesStatus, bluetoothStatus: bluetoothStatus)
54
+ }
55
+
56
+ public var glasses: GlassesRuntimeState {
57
+ state.glasses
58
+ }
59
+
60
+ public var sdkState: PhoneSdkRuntimeState {
61
+ state.sdk
62
+ }
63
+
64
+ public var scanState: BluetoothScanState {
65
+ state.scan
66
+ }
67
+
68
+ var glassesStatus: GlassesStatus {
2062
69
  GlassesStatus(values: DeviceStore.shared.store.getCategory("glasses"))
2063
70
  }
2064
71
 
2065
- public var bluetoothStatus: BluetoothStatus {
72
+ var bluetoothStatus: BluetoothStatus {
2066
73
  BluetoothStatus(values: DeviceStore.shared.store.getCategory(ObservableStore.bluetoothCategory))
2067
74
  }
2068
75
 
@@ -2298,7 +305,7 @@ public final class MentraBluetoothSDK {
2298
305
  public func setMicState(
2299
306
  enabled: Bool,
2300
307
  useGlassesMic: Bool = true,
2301
- bypassVad: Bool = false,
308
+ bypassVad: Bool = true,
2302
309
  sendTranscript: Bool = false,
2303
310
  sendLc3Data: Bool = false
2304
311
  ) {
@@ -2459,9 +466,14 @@ public final class MentraBluetoothSDK {
2459
466
  private func dispatchStoreUpdate(_ category: String, _ changes: [String: Any]) {
2460
467
  switch ObservableStore.normalizeCategory(category) {
2461
468
  case "glasses":
2462
- delegate?.mentraBluetoothSDK(self, didUpdateGlassesStatus: GlassesStatusUpdate(values: glassesStatusChanges(changes)))
469
+ let nextState = state
470
+ delegate?.mentraBluetoothSDK(self, didUpdate: nextState)
471
+ delegate?.mentraBluetoothSDK(self, didUpdateGlasses: nextState.glasses)
2463
472
  case ObservableStore.bluetoothCategory:
2464
- delegate?.mentraBluetoothSDK(self, didUpdateBluetoothStatus: BluetoothStatusUpdate(values: changes))
473
+ let nextState = state
474
+ delegate?.mentraBluetoothSDK(self, didUpdate: nextState)
475
+ delegate?.mentraBluetoothSDK(self, didUpdateSdkState: nextState.sdk)
476
+ delegate?.mentraBluetoothSDK(self, didUpdateScan: nextState.scan)
2465
477
  if !suppressDefaultDeviceEvents && changes.keys.contains(where: { defaultDeviceKeys.contains($0) }) {
2466
478
  dispatchDefaultDeviceChanged()
2467
479
  }
@@ -2472,28 +484,6 @@ public final class MentraBluetoothSDK {
2472
484
  }
2473
485
  }
2474
486
 
2475
- private func glassesStatusChanges(_ changes: [String: Any]) -> [String: Any] {
2476
- var merged = changes
2477
-
2478
- if changes.keys.contains(where: { ["wifiConnected", "wifiSsid", "wifiLocalIp"].contains($0) }) {
2479
- merged["wifiConnected"] = DeviceStore.shared.get("glasses", "wifiConnected") as? Bool ?? false
2480
- merged["wifiSsid"] = DeviceStore.shared.get("glasses", "wifiSsid") as? String ?? ""
2481
- merged["wifiLocalIp"] = DeviceStore.shared.get("glasses", "wifiLocalIp") as? String ?? ""
2482
- }
2483
-
2484
- if changes.keys.contains(where: { ["connected", "fullyBooted", "connectionState"].contains($0) }) {
2485
- merged["connected"] = DeviceStore.shared.get("glasses", "connected") as? Bool ?? false
2486
- merged["fullyBooted"] = DeviceStore.shared.get("glasses", "fullyBooted") as? Bool ?? false
2487
- merged["connectionState"] = DeviceStore.shared.get("glasses", "connectionState") as? String ?? "DISCONNECTED"
2488
- }
2489
-
2490
- if changes["signalStrengthUpdatedAt"] != nil, changes["signalStrength"] == nil {
2491
- merged["signalStrength"] = DeviceStore.shared.get("glasses", "signalStrength") as? Int ?? -1
2492
- }
2493
-
2494
- return merged
2495
- }
2496
-
2497
487
  private func dispatchDefaultDeviceChanged() {
2498
488
  delegate?.mentraBluetoothSDK(self, didChangeDefaultDevice: currentDefaultDevice())
2499
489
  }
@@ -2567,12 +557,14 @@ public final class MentraBluetoothSDK {
2567
557
  case "touch_event":
2568
558
  delegate?.mentraBluetoothSDK(self, didReceive: .touch(TouchEvent(values: data)))
2569
559
  case "mic_pcm":
2570
- if let frame = data["pcm"] as? Data {
2571
- delegate?.mentraBluetoothSDK(self, didReceiveMicPcm: frame)
560
+ let event = MicPcmEvent(values: data)
561
+ if !event.pcm.isEmpty {
562
+ delegate?.mentraBluetoothSDK(self, didReceiveMicPcm: event)
2572
563
  }
2573
564
  case "mic_lc3":
2574
- if let frame = data["lc3"] as? Data {
2575
- delegate?.mentraBluetoothSDK(self, didReceiveMicLc3: frame)
565
+ let event = MicLc3Event(values: data)
566
+ if !event.lc3.isEmpty {
567
+ delegate?.mentraBluetoothSDK(self, didReceiveMicLc3: event)
2576
568
  }
2577
569
  case "local_transcription":
2578
570
  let event = LocalTranscriptionEvent(