@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,359 @@
1
+ import Foundation
2
+
3
+ public struct MentraBluetoothState: CustomStringConvertible {
4
+ public let glasses: GlassesRuntimeState
5
+ public let sdk: PhoneSdkRuntimeState
6
+ public let scan: BluetoothScanState
7
+
8
+ public init(glasses: GlassesRuntimeState, sdk: PhoneSdkRuntimeState, scan: BluetoothScanState) {
9
+ self.glasses = glasses
10
+ self.sdk = sdk
11
+ self.scan = scan
12
+ }
13
+
14
+ init(glassesStatus: GlassesStatus, bluetoothStatus: BluetoothStatus) {
15
+ glasses = GlassesRuntimeState(status: glassesStatus)
16
+ sdk = PhoneSdkRuntimeState(status: bluetoothStatus)
17
+ scan = BluetoothScanState(status: bluetoothStatus)
18
+ }
19
+
20
+ public var description: String {
21
+ "MentraBluetoothState(glasses: \(glasses), scan: \(scan))"
22
+ }
23
+ }
24
+
25
+ public struct GlassesBatteryState: Equatable, CustomStringConvertible {
26
+ public let charging: Bool
27
+ public let level: Int?
28
+
29
+ public init(charging: Bool, level: Int?) {
30
+ self.charging = charging
31
+ self.level = level
32
+ }
33
+
34
+ public var description: String {
35
+ "GlassesBatteryState(level: \(level.map(String.init) ?? "unknown"), charging: \(charging))"
36
+ }
37
+ }
38
+
39
+ public struct ConnectedGlassesInfo: Equatable, CustomStringConvertible {
40
+ public let appVersion: String?
41
+ public let bluetoothName: String?
42
+ public let buildNumber: String?
43
+ public let color: String?
44
+ public let deviceModel: DeviceModel?
45
+ public let firmwareVersion: String?
46
+ public let serialNumber: String?
47
+ public let style: String?
48
+
49
+ public init(
50
+ appVersion: String?,
51
+ bluetoothName: String?,
52
+ buildNumber: String?,
53
+ color: String?,
54
+ deviceModel: DeviceModel?,
55
+ firmwareVersion: String?,
56
+ serialNumber: String?,
57
+ style: String?
58
+ ) {
59
+ self.appVersion = appVersion
60
+ self.bluetoothName = bluetoothName
61
+ self.buildNumber = buildNumber
62
+ self.color = color
63
+ self.deviceModel = deviceModel
64
+ self.firmwareVersion = firmwareVersion
65
+ self.serialNumber = serialNumber
66
+ self.style = style
67
+ }
68
+
69
+ public var description: String {
70
+ bluetoothName ?? serialNumber ?? deviceModel?.deviceType ?? "Connected glasses"
71
+ }
72
+ }
73
+
74
+ public enum FirmwareSource: String {
75
+ case app
76
+ case bes
77
+ case firmware
78
+ case mtk
79
+ case unknown
80
+ }
81
+
82
+ public struct FirmwareInfo: Equatable, CustomStringConvertible {
83
+ public let appVersion: String?
84
+ public let buildNumber: String?
85
+ public let source: FirmwareSource
86
+ public let version: String?
87
+
88
+ public init(appVersion: String?, buildNumber: String?, source: FirmwareSource, version: String?) {
89
+ self.appVersion = appVersion
90
+ self.buildNumber = buildNumber
91
+ self.source = source
92
+ self.version = version
93
+ }
94
+
95
+ public var description: String {
96
+ version ?? "Unknown firmware"
97
+ }
98
+ }
99
+
100
+ public struct SignalState: Equatable, CustomStringConvertible {
101
+ public let strengthDbm: Int?
102
+ public let updatedAt: Int?
103
+
104
+ public init(strengthDbm: Int?, updatedAt: Int?) {
105
+ self.strengthDbm = strengthDbm
106
+ self.updatedAt = updatedAt
107
+ }
108
+
109
+ public var description: String {
110
+ strengthDbm.map { "\($0) dBm" } ?? "Unknown signal"
111
+ }
112
+ }
113
+
114
+ public enum GlassesRuntimeState: CustomStringConvertible {
115
+ case disconnected(connection: GlassesConnectionState)
116
+ case connected(
117
+ battery: GlassesBatteryState,
118
+ connection: GlassesConnectionState,
119
+ device: ConnectedGlassesInfo,
120
+ firmware: FirmwareInfo,
121
+ hotspot: HotspotStatus,
122
+ ready: Bool,
123
+ signal: SignalState,
124
+ voiceActivityDetectionEnabled: Bool,
125
+ wifi: WifiStatus
126
+ )
127
+
128
+ init(status: GlassesStatus) {
129
+ if !status.connected, status.connectionState != .connected {
130
+ self = .disconnected(connection: status.connectionState)
131
+ return
132
+ }
133
+
134
+ self = .connected(
135
+ battery: GlassesBatteryState(
136
+ charging: status.charging,
137
+ level: status.batteryLevel >= 0 ? status.batteryLevel : nil
138
+ ),
139
+ connection: .connected,
140
+ device: ConnectedGlassesInfo(
141
+ appVersion: status.appVersion.nonEmpty,
142
+ bluetoothName: status.bluetoothName.nonEmpty,
143
+ buildNumber: status.buildNumber.nonEmpty,
144
+ color: status.color.nonEmpty,
145
+ deviceModel: status.deviceModel.nonEmpty.map(DeviceModel.fromDeviceType),
146
+ firmwareVersion: status.firmwareVersion.nonEmpty,
147
+ serialNumber: status.serialNumber.nonEmpty,
148
+ style: status.style.nonEmpty
149
+ ),
150
+ firmware: FirmwareInfo(status: status),
151
+ hotspot: status.hotspot,
152
+ ready: status.fullyBooted,
153
+ signal: SignalState(
154
+ strengthDbm: status.signalStrength == -1 ? nil : status.signalStrength,
155
+ updatedAt: status.signalStrengthUpdatedAt <= 0 ? nil : status.signalStrengthUpdatedAt
156
+ ),
157
+ voiceActivityDetectionEnabled: status.voiceActivityDetectionEnabled,
158
+ wifi: status.wifi
159
+ )
160
+ }
161
+
162
+ public var connected: Bool {
163
+ if case .connected = self {
164
+ return true
165
+ }
166
+ return false
167
+ }
168
+
169
+ public var connection: GlassesConnectionState {
170
+ switch self {
171
+ case let .disconnected(connection):
172
+ connection
173
+ case let .connected(_, connection, _, _, _, _, _, _, _):
174
+ connection
175
+ }
176
+ }
177
+
178
+ public var ready: Bool {
179
+ switch self {
180
+ case .disconnected:
181
+ false
182
+ case let .connected(_, _, _, _, _, ready, _, _, _):
183
+ ready
184
+ }
185
+ }
186
+
187
+ public var battery: GlassesBatteryState? {
188
+ guard case let .connected(battery, _, _, _, _, _, _, _, _) = self else {
189
+ return nil
190
+ }
191
+ return battery
192
+ }
193
+
194
+ public var device: ConnectedGlassesInfo? {
195
+ guard case let .connected(_, _, device, _, _, _, _, _, _) = self else {
196
+ return nil
197
+ }
198
+ return device
199
+ }
200
+
201
+ public var firmware: FirmwareInfo? {
202
+ guard case let .connected(_, _, _, firmware, _, _, _, _, _) = self else {
203
+ return nil
204
+ }
205
+ return firmware
206
+ }
207
+
208
+ public var hotspot: HotspotStatus? {
209
+ guard case let .connected(_, _, _, _, hotspot, _, _, _, _) = self else {
210
+ return nil
211
+ }
212
+ return hotspot
213
+ }
214
+
215
+ public var signal: SignalState? {
216
+ guard case let .connected(_, _, _, _, _, _, signal, _, _) = self else {
217
+ return nil
218
+ }
219
+ return signal
220
+ }
221
+
222
+ public var voiceActivityDetectionEnabled: Bool {
223
+ guard case let .connected(_, _, _, _, _, _, _, voiceActivityDetectionEnabled, _) = self else {
224
+ return false
225
+ }
226
+ return voiceActivityDetectionEnabled
227
+ }
228
+
229
+ public var wifi: WifiStatus? {
230
+ guard case let .connected(_, _, _, _, _, _, _, _, wifi) = self else {
231
+ return nil
232
+ }
233
+ return wifi
234
+ }
235
+
236
+ public var description: String {
237
+ switch self {
238
+ case let .disconnected(connection):
239
+ "GlassesRuntimeState(\(connection.rawValue))"
240
+ case let .connected(_, _, device, _, _, ready, _, _, _):
241
+ "GlassesRuntimeState(connected: \(device), ready: \(ready))"
242
+ }
243
+ }
244
+ }
245
+
246
+ public struct GalleryModeState: Equatable {
247
+ public let enabled: Bool
248
+
249
+ public init(enabled: Bool) {
250
+ self.enabled = enabled
251
+ }
252
+ }
253
+
254
+ public enum MicMode: String {
255
+ case phone
256
+ case glasses
257
+ case bluetoothClassic
258
+ case bluetooth
259
+ }
260
+
261
+ public struct PhoneSdkRuntimeState: CustomStringConvertible {
262
+ public let currentMic: MicMode?
263
+ public let defaultDevice: Device?
264
+ public let galleryMode: GalleryModeState
265
+ public let lastLog: [String]
266
+ public let micRanking: [MicMode]
267
+ public let otherBluetoothConnected: Bool
268
+ public let searching: Bool
269
+ public let searchingController: Bool
270
+ public let systemMicUnavailable: Bool
271
+ public let wifiScanResults: [WifiScanResult]
272
+
273
+ public init(
274
+ currentMic: MicMode?,
275
+ defaultDevice: Device?,
276
+ galleryMode: GalleryModeState,
277
+ lastLog: [String],
278
+ micRanking: [MicMode],
279
+ otherBluetoothConnected: Bool,
280
+ searching: Bool,
281
+ searchingController: Bool,
282
+ systemMicUnavailable: Bool,
283
+ wifiScanResults: [WifiScanResult]
284
+ ) {
285
+ self.currentMic = currentMic
286
+ self.defaultDevice = defaultDevice
287
+ self.galleryMode = galleryMode
288
+ self.lastLog = lastLog
289
+ self.micRanking = micRanking
290
+ self.otherBluetoothConnected = otherBluetoothConnected
291
+ self.searching = searching
292
+ self.searchingController = searchingController
293
+ self.systemMicUnavailable = systemMicUnavailable
294
+ self.wifiScanResults = wifiScanResults
295
+ }
296
+
297
+ init(status: BluetoothStatus) {
298
+ currentMic = MicMode(rawValue: status.currentMic)
299
+ defaultDevice = status.defaultDevice
300
+ galleryMode = GalleryModeState(enabled: status.galleryModeEnabled)
301
+ lastLog = status.lastLog
302
+ micRanking = status.micRanking.compactMap(MicMode.init(rawValue:))
303
+ otherBluetoothConnected = status.otherBtConnected
304
+ searching = status.searching
305
+ searchingController = status.searchingController
306
+ systemMicUnavailable = status.systemMicUnavailable
307
+ wifiScanResults = status.wifiScanResults
308
+ }
309
+
310
+ public var description: String {
311
+ "PhoneSdkRuntimeState(searching: \(searching), currentMic: \(currentMic?.rawValue ?? "unknown"))"
312
+ }
313
+ }
314
+
315
+ public struct BluetoothScanState: CustomStringConvertible {
316
+ public let active: Bool
317
+ public let devices: [Device]
318
+ public let searchingController: Bool
319
+
320
+ public init(active: Bool, devices: [Device], searchingController: Bool) {
321
+ self.active = active
322
+ self.devices = devices
323
+ self.searchingController = searchingController
324
+ }
325
+
326
+ init(status: BluetoothStatus) {
327
+ active = status.searching
328
+ devices = status.searchResults
329
+ searchingController = status.searchingController
330
+ }
331
+
332
+ public var description: String {
333
+ "BluetoothScanState(active: \(active), devices: \(devices.count))"
334
+ }
335
+ }
336
+
337
+ private extension FirmwareInfo {
338
+ init(status: GlassesStatus) {
339
+ let sources: [(String, FirmwareSource)] = [
340
+ (status.firmwareVersion, .firmware),
341
+ (status.besFirmwareVersion, .bes),
342
+ (status.mtkFirmwareVersion, .mtk),
343
+ (status.appVersion, .app),
344
+ ]
345
+ let match = sources.first { !$0.0.isEmpty }
346
+ self.init(
347
+ appVersion: status.appVersion.nonEmpty,
348
+ buildNumber: status.buildNumber.nonEmpty,
349
+ source: match?.1 ?? .unknown,
350
+ version: match?.0.nonEmpty
351
+ )
352
+ }
353
+ }
354
+
355
+ private extension String {
356
+ var nonEmpty: String? {
357
+ isEmpty ? nil : self
358
+ }
359
+ }
@@ -0,0 +1,326 @@
1
+ import Foundation
2
+
3
+ public enum WifiStatus: CustomStringConvertible, Equatable {
4
+ public enum State: String {
5
+ case disconnected
6
+ case connected
7
+ }
8
+
9
+ case disconnected
10
+ case connected(ssid: String, localIp: String?)
11
+
12
+ private init?(connected: Bool, ssid: String?, localIp: String?) {
13
+ if connected {
14
+ guard
15
+ let ssid = ssid?.trimmingCharacters(in: .whitespacesAndNewlines),
16
+ !ssid.isEmpty
17
+ else {
18
+ return nil
19
+ }
20
+ let trimmedLocalIp = localIp?.trimmingCharacters(in: .whitespacesAndNewlines)
21
+ self = .connected(
22
+ ssid: ssid,
23
+ localIp: trimmedLocalIp?.isEmpty == false ? trimmedLocalIp : nil
24
+ )
25
+ } else {
26
+ self = .disconnected
27
+ }
28
+ }
29
+
30
+ init?(values: [String: Any]) {
31
+ if let nested = values["wifi"] as? [String: Any] {
32
+ guard let wifi = WifiStatus(values: nested) else {
33
+ return nil
34
+ }
35
+ self = wifi
36
+ return
37
+ }
38
+
39
+ if let state = stringValue(values, "state")?.lowercased() {
40
+ switch state {
41
+ case State.connected.rawValue:
42
+ guard let wifi = WifiStatus(
43
+ connected: true,
44
+ ssid: nonEmptyStringValue(values, "ssid"),
45
+ localIp: nonEmptyStringValue(values, "localIp")
46
+ ) else {
47
+ return nil
48
+ }
49
+ self = wifi
50
+ case State.disconnected.rawValue:
51
+ self = .disconnected
52
+ default:
53
+ return nil
54
+ }
55
+ return
56
+ }
57
+
58
+ return nil
59
+ }
60
+
61
+ static func fromStoreValues(_ values: [String: Any]) -> WifiStatus? {
62
+ guard let connected = boolValue(values, "wifiConnected") else { return nil }
63
+ return fromStoreFields(
64
+ connected: connected,
65
+ ssid: nonEmptyStringValue(values, "wifiSsid"),
66
+ localIp: nonEmptyStringValue(values, "wifiLocalIp")
67
+ )
68
+ }
69
+
70
+ static func fromStoreFields(connected: Bool, ssid: String?, localIp: String?) -> WifiStatus? {
71
+ WifiStatus(connected: connected, ssid: ssid, localIp: localIp)
72
+ }
73
+
74
+ public var state: State {
75
+ switch self {
76
+ case .disconnected:
77
+ .disconnected
78
+ case .connected:
79
+ .connected
80
+ }
81
+ }
82
+
83
+ public var isConnected: Bool {
84
+ if case .connected = self {
85
+ return true
86
+ }
87
+ return false
88
+ }
89
+
90
+ public var values: [String: Any] {
91
+ switch self {
92
+ case .disconnected:
93
+ return ["state": State.disconnected.rawValue]
94
+ case let .connected(ssid, localIp):
95
+ var values: [String: Any] = [
96
+ "state": State.connected.rawValue,
97
+ "ssid": ssid,
98
+ ]
99
+ if let localIp = localIp {
100
+ values["localIp"] = localIp
101
+ }
102
+ return values
103
+ }
104
+ }
105
+
106
+ var storeValues: [String: Any] {
107
+ switch self {
108
+ case .disconnected:
109
+ [
110
+ "wifiConnected": false,
111
+ "wifiSsid": "",
112
+ "wifiLocalIp": "",
113
+ ]
114
+ case let .connected(ssid, localIp):
115
+ [
116
+ "wifiConnected": true,
117
+ "wifiSsid": ssid,
118
+ "wifiLocalIp": localIp ?? "",
119
+ ]
120
+ }
121
+ }
122
+
123
+ public var description: String {
124
+ switch self {
125
+ case .disconnected:
126
+ "WifiStatus(disconnected)"
127
+ case let .connected(ssid, localIp):
128
+ "WifiStatus(connected: \(ssid), localIp: \(localIp ?? "unknown"))"
129
+ }
130
+ }
131
+ }
132
+
133
+ public struct WifiStatusEvent: CustomStringConvertible {
134
+ public let status: WifiStatus
135
+
136
+ public init(status: WifiStatus) {
137
+ self.status = status
138
+ }
139
+
140
+ init(connected: Bool, ssid: String?, localIp: String?) {
141
+ self.status = WifiStatus.fromStoreFields(connected: connected, ssid: ssid, localIp: localIp) ?? .disconnected
142
+ }
143
+
144
+ init(values: [String: Any]) {
145
+ self.status = WifiStatus(values: values) ?? .disconnected
146
+ }
147
+
148
+ public var values: [String: Any] {
149
+ status.values.merging(["type": "wifi_status_change"]) { _, new in new }
150
+ }
151
+
152
+ public var description: String {
153
+ "WifiStatusEvent(\(status))"
154
+ }
155
+ }
156
+
157
+ public enum HotspotStatus: CustomStringConvertible, Equatable {
158
+ public enum State: String {
159
+ case disabled
160
+ case enabled
161
+ }
162
+
163
+ case disabled
164
+ case enabled(ssid: String, password: String, localIp: String)
165
+
166
+ public init?(enabled: Bool, ssid: String?, password: String?, localIp: String?) {
167
+ if enabled {
168
+ guard
169
+ let ssid = ssid?.trimmingCharacters(in: .whitespacesAndNewlines),
170
+ !ssid.isEmpty,
171
+ let password = password?.trimmingCharacters(in: .whitespacesAndNewlines),
172
+ !password.isEmpty,
173
+ let localIp = localIp?.trimmingCharacters(in: .whitespacesAndNewlines),
174
+ !localIp.isEmpty
175
+ else {
176
+ return nil
177
+ }
178
+ self = .enabled(ssid: ssid, password: password, localIp: localIp)
179
+ } else {
180
+ self = .disabled
181
+ }
182
+ }
183
+
184
+ public init?(values: [String: Any]) {
185
+ if let nested = values["hotspot"] as? [String: Any] {
186
+ self.init(values: nested)
187
+ return
188
+ }
189
+
190
+ guard let state = stringValue(values, "state")?.lowercased() else {
191
+ return nil
192
+ }
193
+
194
+ switch state {
195
+ case State.enabled.rawValue:
196
+ self.init(
197
+ enabled: true,
198
+ ssid: nonEmptyStringValue(values, "ssid"),
199
+ password: nonEmptyStringValue(values, "password"),
200
+ localIp: nonEmptyStringValue(values, "localIp")
201
+ )
202
+ case State.disabled.rawValue:
203
+ self = .disabled
204
+ default:
205
+ return nil
206
+ }
207
+ }
208
+
209
+ static func fromStoreValues(_ values: [String: Any]) -> HotspotStatus? {
210
+ guard let enabled = boolValue(values, "hotspotEnabled") else {
211
+ return nil
212
+ }
213
+ return fromStoreFields(
214
+ enabled: enabled,
215
+ ssid: nonEmptyStringValue(values, "hotspotSsid"),
216
+ password: nonEmptyStringValue(values, "hotspotPassword"),
217
+ localIp: nonEmptyStringValue(values, "hotspotGatewayIp")
218
+ )
219
+ }
220
+
221
+ static func fromStoreFields(enabled: Bool, ssid: String?, password: String?, localIp: String?) -> HotspotStatus? {
222
+ HotspotStatus(enabled: enabled, ssid: ssid, password: password, localIp: localIp)
223
+ }
224
+
225
+ var storeValues: [String: Any] {
226
+ switch self {
227
+ case .disabled:
228
+ [
229
+ "hotspotEnabled": false,
230
+ "hotspotSsid": "",
231
+ "hotspotPassword": "",
232
+ "hotspotGatewayIp": "",
233
+ ]
234
+ case let .enabled(ssid, password, localIp):
235
+ [
236
+ "hotspotEnabled": true,
237
+ "hotspotSsid": ssid,
238
+ "hotspotPassword": password,
239
+ "hotspotGatewayIp": localIp,
240
+ ]
241
+ }
242
+ }
243
+
244
+ public var values: [String: Any] {
245
+ switch self {
246
+ case .disabled:
247
+ ["state": State.disabled.rawValue]
248
+ case let .enabled(ssid, password, localIp):
249
+ [
250
+ "state": State.enabled.rawValue,
251
+ "ssid": ssid,
252
+ "password": password,
253
+ "localIp": localIp,
254
+ ]
255
+ }
256
+ }
257
+
258
+ public var state: State {
259
+ switch self {
260
+ case .disabled:
261
+ .disabled
262
+ case .enabled:
263
+ .enabled
264
+ }
265
+ }
266
+
267
+ public var isEnabled: Bool {
268
+ if case .enabled = self {
269
+ return true
270
+ }
271
+ return false
272
+ }
273
+
274
+ public var description: String {
275
+ switch self {
276
+ case .disabled:
277
+ "HotspotStatus(disabled)"
278
+ case let .enabled(ssid, _, localIp):
279
+ "HotspotStatus(enabled: \(ssid), localIp: \(localIp))"
280
+ }
281
+ }
282
+ }
283
+
284
+ public struct HotspotStatusEvent: CustomStringConvertible {
285
+ public let status: HotspotStatus
286
+
287
+ public init(status: HotspotStatus) {
288
+ self.status = status
289
+ }
290
+
291
+ init(enabled: Bool, ssid: String?, password: String?, localIp: String?) {
292
+ self.status = HotspotStatus.fromStoreFields(enabled: enabled, ssid: ssid, password: password, localIp: localIp) ?? .disabled
293
+ }
294
+
295
+ init(values: [String: Any]) {
296
+ self.status = HotspotStatus(values: values) ?? .disabled
297
+ }
298
+
299
+ public var values: [String: Any] {
300
+ status.values.merging(["type": "hotspot_status_change"]) { _, new in new }
301
+ }
302
+
303
+ public var description: String {
304
+ "HotspotStatusEvent(\(status))"
305
+ }
306
+ }
307
+
308
+ public struct HotspotErrorEvent: CustomStringConvertible {
309
+ public let values: [String: Any]
310
+
311
+ public init(values: [String: Any]) {
312
+ self.values = values
313
+ }
314
+
315
+ public var message: String? {
316
+ stringValue(values, "errorMessage")
317
+ }
318
+
319
+ public var timestamp: Int? {
320
+ intValue(values["timestamp"])
321
+ }
322
+
323
+ public var description: String {
324
+ "HotspotErrorEvent(message: \(message ?? "unknown"))"
325
+ }
326
+ }