@mentra/bluetooth-sdk 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/README.md +61 -42
  2. package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +20 -12
  3. package/android/src/main/java/com/mentra/bluetoothsdk/Bridge.kt +38 -12
  4. package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +2 -1
  5. package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +45 -51
  6. package/android/src/main/java/com/mentra/bluetoothsdk/audio/AudioModels.kt +125 -0
  7. package/android/src/main/java/com/mentra/bluetoothsdk/camera/CameraModels.kt +187 -0
  8. package/android/src/main/java/com/mentra/bluetoothsdk/connection/ConnectionModels.kt +78 -0
  9. package/android/src/main/java/com/mentra/bluetoothsdk/events/BluetoothEvents.kt +60 -0
  10. package/android/src/main/java/com/mentra/bluetoothsdk/internal/MapParsing.kt +132 -0
  11. package/android/src/main/java/com/mentra/bluetoothsdk/requests/DisplayRequests.kt +39 -0
  12. package/android/src/main/java/com/mentra/bluetoothsdk/status/DeviceStatus.kt +605 -0
  13. package/android/src/main/java/com/mentra/bluetoothsdk/status/RuntimeState.kt +199 -0
  14. package/android/src/main/java/com/mentra/bluetoothsdk/status/WifiHotspotStatus.kt +173 -0
  15. package/android/src/main/java/com/mentra/bluetoothsdk/streaming/StreamModels.kt +348 -0
  16. package/android/src/main/java/com/mentra/bluetoothsdk/types/DeviceModels.kt +75 -0
  17. package/build/BluetoothSdk.types.d.ts +24 -7
  18. package/build/BluetoothSdk.types.d.ts.map +1 -1
  19. package/build/BluetoothSdk.types.js.map +1 -1
  20. package/build/_internal.d.ts +1 -1
  21. package/build/_internal.js +1 -1
  22. package/build/_internal.js.map +1 -1
  23. package/build/_private/BluetoothSdkModule.d.ts +5 -4
  24. package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
  25. package/build/_private/BluetoothSdkModule.js +4 -1
  26. package/build/_private/BluetoothSdkModule.js.map +1 -1
  27. package/build/index.d.ts +4 -3
  28. package/build/index.d.ts.map +1 -1
  29. package/build/index.js +81 -2
  30. package/build/index.js.map +1 -1
  31. package/build/react/index.d.ts +2 -2
  32. package/build/react/index.d.ts.map +1 -1
  33. package/build/react/index.js +1 -2
  34. package/build/react/index.js.map +1 -1
  35. package/build/react/useBluetoothStatus.js +1 -1
  36. package/build/react/useBluetoothStatus.js.map +1 -1
  37. package/build/react/useGlassesConnection.d.ts.map +1 -1
  38. package/build/react/useGlassesConnection.js +13 -0
  39. package/build/react/useGlassesConnection.js.map +1 -1
  40. package/build/react/useMentraBluetooth.d.ts +98 -0
  41. package/build/react/useMentraBluetooth.d.ts.map +1 -0
  42. package/build/react/useMentraBluetooth.js +156 -0
  43. package/build/react/useMentraBluetooth.js.map +1 -0
  44. package/ios/BluetoothSdkModule.swift +9 -9
  45. package/ios/Source/Audio/AudioModels.swift +159 -0
  46. package/ios/Source/Bridge.swift +42 -8
  47. package/ios/Source/Camera/CameraModels.swift +246 -0
  48. package/ios/Source/Connection/ScanSession.swift +27 -0
  49. package/ios/Source/DeviceManager.swift +0 -1
  50. package/ios/Source/DeviceStore.swift +2 -1
  51. package/ios/Source/Errors/BluetoothError.swift +19 -0
  52. package/ios/Source/Events/BluetoothEvents.swift +115 -0
  53. package/ios/Source/Internal/BluetoothAvailability.swift +58 -0
  54. package/ios/Source/Internal/ValueParsing.swift +90 -0
  55. package/ios/Source/MentraBluetoothSDK.swift +32 -2040
  56. package/ios/Source/Requests/DisplayRequests.swift +58 -0
  57. package/ios/Source/Status/DeviceStatus.swift +463 -0
  58. package/ios/Source/Status/RuntimeState.swift +350 -0
  59. package/ios/Source/Status/WifiHotspotStatus.swift +326 -0
  60. package/ios/Source/Streaming/StreamModels.swift +436 -0
  61. package/ios/Source/Types/DeviceModels.swift +134 -0
  62. package/ios/Source/sgcs/G2.swift +97 -36
  63. package/package.json +1 -1
  64. package/src/BluetoothSdk.types.ts +24 -7
  65. package/src/_internal.ts +1 -1
  66. package/src/_private/BluetoothSdkModule.ts +10 -5
  67. package/src/index.ts +93 -4
  68. package/src/react/index.ts +13 -11
  69. package/src/react/useBluetoothStatus.ts +1 -1
  70. package/src/react/useGlassesConnection.ts +15 -0
  71. package/src/react/useMentraBluetooth.ts +307 -0
  72. package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothModels.kt +0 -1725
@@ -0,0 +1,350 @@
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
+ wifi: WifiStatus
125
+ )
126
+
127
+ init(status: GlassesStatus) {
128
+ if !status.connected, status.connectionState != .connected {
129
+ self = .disconnected(connection: status.connectionState)
130
+ return
131
+ }
132
+
133
+ self = .connected(
134
+ battery: GlassesBatteryState(
135
+ charging: status.charging,
136
+ level: status.batteryLevel >= 0 ? status.batteryLevel : nil
137
+ ),
138
+ connection: .connected,
139
+ device: ConnectedGlassesInfo(
140
+ appVersion: status.appVersion.nonEmpty,
141
+ bluetoothName: status.bluetoothName.nonEmpty,
142
+ buildNumber: status.buildNumber.nonEmpty,
143
+ color: status.color.nonEmpty,
144
+ deviceModel: status.deviceModel.nonEmpty.map(DeviceModel.fromDeviceType),
145
+ firmwareVersion: status.firmwareVersion.nonEmpty,
146
+ serialNumber: status.serialNumber.nonEmpty,
147
+ style: status.style.nonEmpty
148
+ ),
149
+ firmware: FirmwareInfo(status: status),
150
+ hotspot: status.hotspot,
151
+ ready: status.fullyBooted,
152
+ signal: SignalState(
153
+ strengthDbm: status.signalStrength == -1 ? nil : status.signalStrength,
154
+ updatedAt: status.signalStrengthUpdatedAt <= 0 ? nil : status.signalStrengthUpdatedAt
155
+ ),
156
+ wifi: status.wifi
157
+ )
158
+ }
159
+
160
+ public var connected: Bool {
161
+ if case .connected = self {
162
+ return true
163
+ }
164
+ return false
165
+ }
166
+
167
+ public var connection: GlassesConnectionState {
168
+ switch self {
169
+ case let .disconnected(connection):
170
+ connection
171
+ case let .connected(_, connection, _, _, _, _, _, _):
172
+ connection
173
+ }
174
+ }
175
+
176
+ public var ready: Bool {
177
+ switch self {
178
+ case .disconnected:
179
+ false
180
+ case let .connected(_, _, _, _, _, ready, _, _):
181
+ ready
182
+ }
183
+ }
184
+
185
+ public var battery: GlassesBatteryState? {
186
+ guard case let .connected(battery, _, _, _, _, _, _, _) = self else {
187
+ return nil
188
+ }
189
+ return battery
190
+ }
191
+
192
+ public var device: ConnectedGlassesInfo? {
193
+ guard case let .connected(_, _, device, _, _, _, _, _) = self else {
194
+ return nil
195
+ }
196
+ return device
197
+ }
198
+
199
+ public var firmware: FirmwareInfo? {
200
+ guard case let .connected(_, _, _, firmware, _, _, _, _) = self else {
201
+ return nil
202
+ }
203
+ return firmware
204
+ }
205
+
206
+ public var hotspot: HotspotStatus? {
207
+ guard case let .connected(_, _, _, _, hotspot, _, _, _) = self else {
208
+ return nil
209
+ }
210
+ return hotspot
211
+ }
212
+
213
+ public var signal: SignalState? {
214
+ guard case let .connected(_, _, _, _, _, _, signal, _) = self else {
215
+ return nil
216
+ }
217
+ return signal
218
+ }
219
+
220
+ public var wifi: WifiStatus? {
221
+ guard case let .connected(_, _, _, _, _, _, _, wifi) = self else {
222
+ return nil
223
+ }
224
+ return wifi
225
+ }
226
+
227
+ public var description: String {
228
+ switch self {
229
+ case let .disconnected(connection):
230
+ "GlassesRuntimeState(\(connection.rawValue))"
231
+ case let .connected(_, _, device, _, _, ready, _, _):
232
+ "GlassesRuntimeState(connected: \(device), ready: \(ready))"
233
+ }
234
+ }
235
+ }
236
+
237
+ public struct GalleryModeState: Equatable {
238
+ public let desired: GalleryMode
239
+
240
+ public init(desired: GalleryMode) {
241
+ self.desired = desired
242
+ }
243
+ }
244
+
245
+ public enum MicMode: String {
246
+ case phone
247
+ case glasses
248
+ case bluetoothClassic
249
+ case bluetooth
250
+ }
251
+
252
+ public struct PhoneSdkRuntimeState: CustomStringConvertible {
253
+ public let currentMic: MicMode?
254
+ public let defaultDevice: Device?
255
+ public let galleryMode: GalleryModeState
256
+ public let lastLog: [String]
257
+ public let micRanking: [MicMode]
258
+ public let otherBluetoothConnected: Bool
259
+ public let searching: Bool
260
+ public let searchingController: Bool
261
+ public let systemMicUnavailable: Bool
262
+ public let wifiScanResults: [WifiScanResult]
263
+
264
+ public init(
265
+ currentMic: MicMode?,
266
+ defaultDevice: Device?,
267
+ galleryMode: GalleryModeState,
268
+ lastLog: [String],
269
+ micRanking: [MicMode],
270
+ otherBluetoothConnected: Bool,
271
+ searching: Bool,
272
+ searchingController: Bool,
273
+ systemMicUnavailable: Bool,
274
+ wifiScanResults: [WifiScanResult]
275
+ ) {
276
+ self.currentMic = currentMic
277
+ self.defaultDevice = defaultDevice
278
+ self.galleryMode = galleryMode
279
+ self.lastLog = lastLog
280
+ self.micRanking = micRanking
281
+ self.otherBluetoothConnected = otherBluetoothConnected
282
+ self.searching = searching
283
+ self.searchingController = searchingController
284
+ self.systemMicUnavailable = systemMicUnavailable
285
+ self.wifiScanResults = wifiScanResults
286
+ }
287
+
288
+ init(status: BluetoothStatus) {
289
+ currentMic = MicMode(rawValue: status.currentMic)
290
+ defaultDevice = status.defaultDevice
291
+ galleryMode = GalleryModeState(desired: status.galleryModeAuto ? .auto : .manual)
292
+ lastLog = status.lastLog
293
+ micRanking = status.micRanking.compactMap(MicMode.init(rawValue:))
294
+ otherBluetoothConnected = status.otherBtConnected
295
+ searching = status.searching
296
+ searchingController = status.searchingController
297
+ systemMicUnavailable = status.systemMicUnavailable
298
+ wifiScanResults = status.wifiScanResults
299
+ }
300
+
301
+ public var description: String {
302
+ "PhoneSdkRuntimeState(searching: \(searching), currentMic: \(currentMic?.rawValue ?? "unknown"))"
303
+ }
304
+ }
305
+
306
+ public struct BluetoothScanState: CustomStringConvertible {
307
+ public let active: Bool
308
+ public let devices: [Device]
309
+ public let searchingController: Bool
310
+
311
+ public init(active: Bool, devices: [Device], searchingController: Bool) {
312
+ self.active = active
313
+ self.devices = devices
314
+ self.searchingController = searchingController
315
+ }
316
+
317
+ init(status: BluetoothStatus) {
318
+ active = status.searching
319
+ devices = status.searchResults
320
+ searchingController = status.searchingController
321
+ }
322
+
323
+ public var description: String {
324
+ "BluetoothScanState(active: \(active), devices: \(devices.count))"
325
+ }
326
+ }
327
+
328
+ private extension FirmwareInfo {
329
+ init(status: GlassesStatus) {
330
+ let sources: [(String, FirmwareSource)] = [
331
+ (status.firmwareVersion, .firmware),
332
+ (status.besFirmwareVersion, .bes),
333
+ (status.mtkFirmwareVersion, .mtk),
334
+ (status.appVersion, .app),
335
+ ]
336
+ let match = sources.first { !$0.0.isEmpty }
337
+ self.init(
338
+ appVersion: status.appVersion.nonEmpty,
339
+ buildNumber: status.buildNumber.nonEmpty,
340
+ source: match?.1 ?? .unknown,
341
+ version: match?.0.nonEmpty
342
+ )
343
+ }
344
+ }
345
+
346
+ private extension String {
347
+ var nonEmpty: String? {
348
+ isEmpty ? nil : self
349
+ }
350
+ }
@@ -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
+ }