@mentra/bluetooth-sdk 0.1.5 → 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.
- package/README.md +29 -13
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +15 -7
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +1 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +34 -46
- package/android/src/main/java/com/mentra/bluetoothsdk/audio/AudioModels.kt +125 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/camera/CameraModels.kt +187 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/connection/ConnectionModels.kt +78 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/events/BluetoothEvents.kt +60 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/internal/MapParsing.kt +132 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/requests/DisplayRequests.kt +39 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/status/DeviceStatus.kt +605 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/status/RuntimeState.kt +199 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/status/WifiHotspotStatus.kt +173 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/streaming/StreamModels.kt +348 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/types/DeviceModels.kt +75 -0
- package/build/BluetoothSdk.types.d.ts +4 -0
- package/build/BluetoothSdk.types.d.ts.map +1 -1
- package/build/BluetoothSdk.types.js.map +1 -1
- package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
- package/build/_private/BluetoothSdkModule.js +3 -0
- package/build/_private/BluetoothSdkModule.js.map +1 -1
- package/ios/BluetoothSdkModule.swift +4 -4
- package/ios/Source/Audio/AudioModels.swift +159 -0
- package/ios/Source/Camera/CameraModels.swift +246 -0
- package/ios/Source/Connection/ScanSession.swift +27 -0
- package/ios/Source/DeviceStore.swift +1 -0
- package/ios/Source/Errors/BluetoothError.swift +19 -0
- package/ios/Source/Events/BluetoothEvents.swift +115 -0
- package/ios/Source/Internal/BluetoothAvailability.swift +58 -0
- package/ios/Source/Internal/ValueParsing.swift +90 -0
- package/ios/Source/MentraBluetoothSDK.swift +25 -2140
- package/ios/Source/Requests/DisplayRequests.swift +58 -0
- package/ios/Source/Status/DeviceStatus.swift +463 -0
- package/ios/Source/Status/RuntimeState.swift +350 -0
- package/ios/Source/Status/WifiHotspotStatus.swift +326 -0
- package/ios/Source/Streaming/StreamModels.swift +436 -0
- package/ios/Source/Types/DeviceModels.swift +134 -0
- package/ios/Source/sgcs/G2.swift +97 -36
- package/package.json +1 -1
- package/src/BluetoothSdk.types.ts +4 -0
- package/src/_private/BluetoothSdkModule.ts +3 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothModels.kt +0 -1810
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
public enum GalleryMode {
|
|
4
|
+
case auto
|
|
5
|
+
case manual
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
public enum PhotoSize: String {
|
|
9
|
+
case small
|
|
10
|
+
case medium
|
|
11
|
+
case large
|
|
12
|
+
case full
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
public enum ButtonPhotoSize: String {
|
|
16
|
+
case small
|
|
17
|
+
case medium
|
|
18
|
+
case large
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
public enum PhotoCompression: String {
|
|
22
|
+
case none
|
|
23
|
+
case medium
|
|
24
|
+
case heavy
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
public struct ButtonPhotoSettings {
|
|
28
|
+
public let size: ButtonPhotoSize
|
|
29
|
+
|
|
30
|
+
public init(size: ButtonPhotoSize) {
|
|
31
|
+
self.size = size
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public struct ButtonVideoRecordingSettings {
|
|
36
|
+
public let width: Int
|
|
37
|
+
public let height: Int
|
|
38
|
+
public let frameRate: Int
|
|
39
|
+
|
|
40
|
+
public init(width: Int, height: Int, frameRate: Int) {
|
|
41
|
+
self.width = width
|
|
42
|
+
self.height = height
|
|
43
|
+
self.frameRate = frameRate
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
public enum CameraFov {
|
|
48
|
+
case standard
|
|
49
|
+
case wide
|
|
50
|
+
|
|
51
|
+
var value: [String: Int] {
|
|
52
|
+
switch self {
|
|
53
|
+
case .standard:
|
|
54
|
+
["fov": 118, "roiPosition": 0]
|
|
55
|
+
case .wide:
|
|
56
|
+
["fov": 118, "roiPosition": 0]
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
public struct PhotoRequest {
|
|
62
|
+
public let requestId: String
|
|
63
|
+
public let appId: String
|
|
64
|
+
public let size: PhotoSize
|
|
65
|
+
public let webhookUrl: String?
|
|
66
|
+
public let authToken: String?
|
|
67
|
+
public let compress: PhotoCompression?
|
|
68
|
+
public let sound: Bool
|
|
69
|
+
|
|
70
|
+
public init(
|
|
71
|
+
requestId: String,
|
|
72
|
+
appId: String,
|
|
73
|
+
size: PhotoSize,
|
|
74
|
+
webhookUrl: String? = nil,
|
|
75
|
+
authToken: String? = nil,
|
|
76
|
+
compress: PhotoCompression? = nil,
|
|
77
|
+
sound: Bool
|
|
78
|
+
) {
|
|
79
|
+
self.requestId = requestId
|
|
80
|
+
self.appId = appId
|
|
81
|
+
self.size = size
|
|
82
|
+
self.webhookUrl = webhookUrl
|
|
83
|
+
self.authToken = authToken
|
|
84
|
+
self.compress = compress
|
|
85
|
+
self.sound = sound
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
public enum RgbLedAction: String {
|
|
90
|
+
case on
|
|
91
|
+
case off
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
public enum RgbLedColor: String {
|
|
95
|
+
case red
|
|
96
|
+
case green
|
|
97
|
+
case blue
|
|
98
|
+
case orange
|
|
99
|
+
case white
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
public struct RgbLedRequest {
|
|
103
|
+
public let requestId: String
|
|
104
|
+
public let packageName: String?
|
|
105
|
+
public let action: RgbLedAction
|
|
106
|
+
public let color: RgbLedColor?
|
|
107
|
+
public let onDurationMs: Int
|
|
108
|
+
public let offDurationMs: Int
|
|
109
|
+
public let count: Int
|
|
110
|
+
|
|
111
|
+
public init(
|
|
112
|
+
requestId: String,
|
|
113
|
+
packageName: String?,
|
|
114
|
+
action: RgbLedAction,
|
|
115
|
+
color: RgbLedColor?,
|
|
116
|
+
onDurationMs: Int,
|
|
117
|
+
offDurationMs: Int,
|
|
118
|
+
count: Int
|
|
119
|
+
) {
|
|
120
|
+
self.requestId = requestId
|
|
121
|
+
self.packageName = packageName
|
|
122
|
+
self.action = action
|
|
123
|
+
self.color = color
|
|
124
|
+
self.onDurationMs = onDurationMs
|
|
125
|
+
self.offDurationMs = offDurationMs
|
|
126
|
+
self.count = count
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
public struct VideoRecordingRequest {
|
|
131
|
+
public let requestId: String
|
|
132
|
+
public let save: Bool
|
|
133
|
+
public let sound: Bool
|
|
134
|
+
|
|
135
|
+
public init(requestId: String, save: Bool, sound: Bool) {
|
|
136
|
+
self.requestId = requestId
|
|
137
|
+
self.save = save
|
|
138
|
+
self.sound = sound
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
public enum PhotoResponse: CustomStringConvertible, Equatable {
|
|
143
|
+
public enum State: String {
|
|
144
|
+
case success
|
|
145
|
+
case error
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
case success(requestId: String, uploadUrl: String, timestamp: Int)
|
|
149
|
+
case error(requestId: String, errorCode: String?, errorMessage: String, timestamp: Int)
|
|
150
|
+
|
|
151
|
+
public init(values: [String: Any]) {
|
|
152
|
+
let requestId = stringValue(values, "requestId") ?? ""
|
|
153
|
+
let timestamp = intValue(values["timestamp"]) ?? Int(Date().timeIntervalSince1970 * 1000)
|
|
154
|
+
let state = stringValue(values, "state")?.lowercased()
|
|
155
|
+
if state == State.success.rawValue {
|
|
156
|
+
self = .success(
|
|
157
|
+
requestId: requestId,
|
|
158
|
+
uploadUrl: stringValue(values, "uploadUrl") ?? "",
|
|
159
|
+
timestamp: timestamp
|
|
160
|
+
)
|
|
161
|
+
} else {
|
|
162
|
+
self = .error(
|
|
163
|
+
requestId: requestId,
|
|
164
|
+
errorCode: stringValue(values, "errorCode"),
|
|
165
|
+
errorMessage: stringValue(values, "errorMessage") ?? "Unknown photo error",
|
|
166
|
+
timestamp: timestamp
|
|
167
|
+
)
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
public var state: State {
|
|
172
|
+
switch self {
|
|
173
|
+
case .success:
|
|
174
|
+
.success
|
|
175
|
+
case .error:
|
|
176
|
+
.error
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
public var requestId: String {
|
|
181
|
+
switch self {
|
|
182
|
+
case let .success(requestId, _, _), let .error(requestId, _, _, _):
|
|
183
|
+
requestId
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
public var timestamp: Int {
|
|
188
|
+
switch self {
|
|
189
|
+
case let .success(_, _, timestamp), let .error(_, _, _, timestamp):
|
|
190
|
+
timestamp
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
public var values: [String: Any] {
|
|
195
|
+
switch self {
|
|
196
|
+
case let .success(requestId, uploadUrl, timestamp):
|
|
197
|
+
return [
|
|
198
|
+
"state": State.success.rawValue,
|
|
199
|
+
"requestId": requestId,
|
|
200
|
+
"uploadUrl": uploadUrl,
|
|
201
|
+
"timestamp": timestamp,
|
|
202
|
+
]
|
|
203
|
+
case let .error(requestId, errorCode, errorMessage, timestamp):
|
|
204
|
+
var values: [String: Any] = [
|
|
205
|
+
"state": State.error.rawValue,
|
|
206
|
+
"requestId": requestId,
|
|
207
|
+
"errorMessage": errorMessage,
|
|
208
|
+
"timestamp": timestamp,
|
|
209
|
+
]
|
|
210
|
+
if let errorCode, !errorCode.isEmpty {
|
|
211
|
+
values["errorCode"] = errorCode
|
|
212
|
+
}
|
|
213
|
+
return values
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
public var description: String {
|
|
218
|
+
"PhotoResponse(requestId: \(requestId), state: \(state.rawValue))"
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
public struct PhotoResponseEvent: CustomStringConvertible {
|
|
223
|
+
public let response: PhotoResponse
|
|
224
|
+
|
|
225
|
+
public init(response: PhotoResponse) {
|
|
226
|
+
self.response = response
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
public init(values: [String: Any]) {
|
|
230
|
+
self.response = PhotoResponse(values: values)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
public var requestId: String {
|
|
234
|
+
response.requestId
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
public var values: [String: Any] {
|
|
238
|
+
var values = response.values
|
|
239
|
+
values["type"] = "photo_response"
|
|
240
|
+
return values
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
public var description: String {
|
|
244
|
+
"PhotoResponseEvent(requestId: \(requestId), state: \(response.state.rawValue))"
|
|
245
|
+
}
|
|
246
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
public enum ScanStopReason {
|
|
4
|
+
case completed
|
|
5
|
+
case cancelled
|
|
6
|
+
case error
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
@MainActor
|
|
10
|
+
public final class ScanSession {
|
|
11
|
+
private let stopAction: () -> Void
|
|
12
|
+
private var stopped = false
|
|
13
|
+
|
|
14
|
+
init(stopAction: @escaping () -> Void) {
|
|
15
|
+
self.stopAction = stopAction
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public func stop() {
|
|
19
|
+
guard !stopped else { return }
|
|
20
|
+
stopped = true
|
|
21
|
+
stopAction()
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
func markStopped() {
|
|
25
|
+
stopped = true
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -97,6 +97,7 @@ class DeviceStore {
|
|
|
97
97
|
store.set("bluetooth", "should_send_lc3", false)
|
|
98
98
|
store.set("bluetooth", "should_send_transcript", false)
|
|
99
99
|
store.set("bluetooth", "bypass_vad", true)
|
|
100
|
+
store.set("bluetooth", "use_native_dashboard", false)
|
|
100
101
|
}
|
|
101
102
|
|
|
102
103
|
func get(_ category: String, _ key: String) -> Any? {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
public struct BluetoothError: Error, LocalizedError, CustomStringConvertible {
|
|
4
|
+
public let code: String
|
|
5
|
+
public let message: String
|
|
6
|
+
|
|
7
|
+
public init(code: String, message: String) {
|
|
8
|
+
self.code = code
|
|
9
|
+
self.message = message
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
public var errorDescription: String? {
|
|
13
|
+
message
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
public var description: String {
|
|
17
|
+
"\(code): \(message)"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
public struct ButtonPressEvent: CustomStringConvertible {
|
|
4
|
+
public let buttonId: String
|
|
5
|
+
public let pressType: String
|
|
6
|
+
public let timestamp: Int?
|
|
7
|
+
|
|
8
|
+
public init(buttonId: String, pressType: String, timestamp: Int? = nil) {
|
|
9
|
+
self.buttonId = buttonId
|
|
10
|
+
self.pressType = pressType
|
|
11
|
+
self.timestamp = timestamp
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
public var description: String {
|
|
15
|
+
"ButtonPressEvent(buttonId: \(buttonId), pressType: \(pressType))"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
public struct TouchEvent: CustomStringConvertible {
|
|
20
|
+
public let values: [String: Any]
|
|
21
|
+
|
|
22
|
+
public init(values: [String: Any]) {
|
|
23
|
+
self.values = values
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public var deviceModel: String? {
|
|
27
|
+
stringValue(values, "deviceModel")
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public var gestureName: String? {
|
|
31
|
+
stringValue(values, "gestureName")
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public var timestamp: Int? {
|
|
35
|
+
intValue(values["timestamp"])
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public var isSwipe: Bool {
|
|
39
|
+
gestureName?.localizedCaseInsensitiveContains("swipe") == true
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public var description: String {
|
|
43
|
+
"TouchEvent(gestureName: \(gestureName ?? "unknown"))"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
public enum BluetoothEvent: CustomStringConvertible {
|
|
48
|
+
case buttonPress(ButtonPressEvent)
|
|
49
|
+
case touch(TouchEvent)
|
|
50
|
+
case wifiStatus(WifiStatusEvent)
|
|
51
|
+
case hotspotStatus(HotspotStatusEvent)
|
|
52
|
+
case hotspotError(HotspotErrorEvent)
|
|
53
|
+
case photoResponse(PhotoResponseEvent)
|
|
54
|
+
case streamStatus(StreamStatusEvent)
|
|
55
|
+
case keepAliveAck(KeepAliveAckEvent)
|
|
56
|
+
case localTranscription(LocalTranscriptionEvent)
|
|
57
|
+
case raw(name: String, values: [String: Any])
|
|
58
|
+
|
|
59
|
+
public var description: String {
|
|
60
|
+
switch self {
|
|
61
|
+
case let .buttonPress(event):
|
|
62
|
+
event.description
|
|
63
|
+
case let .touch(event):
|
|
64
|
+
event.description
|
|
65
|
+
case let .wifiStatus(event):
|
|
66
|
+
event.description
|
|
67
|
+
case let .hotspotStatus(event):
|
|
68
|
+
event.description
|
|
69
|
+
case let .hotspotError(event):
|
|
70
|
+
event.description
|
|
71
|
+
case let .photoResponse(event):
|
|
72
|
+
event.description
|
|
73
|
+
case let .streamStatus(event):
|
|
74
|
+
event.description
|
|
75
|
+
case let .keepAliveAck(event):
|
|
76
|
+
event.description
|
|
77
|
+
case let .localTranscription(event):
|
|
78
|
+
event.description
|
|
79
|
+
case let .raw(name, values):
|
|
80
|
+
"\(name): \(values)"
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
@MainActor
|
|
86
|
+
public protocol MentraBluetoothSDKDelegate: AnyObject {
|
|
87
|
+
func mentraBluetoothSDK(_ sdk: MentraBluetoothSDK, didUpdate state: MentraBluetoothState)
|
|
88
|
+
func mentraBluetoothSDK(_ sdk: MentraBluetoothSDK, didUpdateGlasses glasses: GlassesRuntimeState)
|
|
89
|
+
func mentraBluetoothSDK(_ sdk: MentraBluetoothSDK, didUpdateSdkState sdkState: PhoneSdkRuntimeState)
|
|
90
|
+
func mentraBluetoothSDK(_ sdk: MentraBluetoothSDK, didUpdateScan scan: BluetoothScanState)
|
|
91
|
+
func mentraBluetoothSDK(_ sdk: MentraBluetoothSDK, didDiscover device: Device)
|
|
92
|
+
func mentraBluetoothSDK(_ sdk: MentraBluetoothSDK, didStopScan reason: ScanStopReason)
|
|
93
|
+
func mentraBluetoothSDK(_ sdk: MentraBluetoothSDK, didReceive event: BluetoothEvent)
|
|
94
|
+
func mentraBluetoothSDK(_ sdk: MentraBluetoothSDK, didReceiveMicPcm event: MicPcmEvent)
|
|
95
|
+
func mentraBluetoothSDK(_ sdk: MentraBluetoothSDK, didReceiveMicLc3 event: MicLc3Event)
|
|
96
|
+
func mentraBluetoothSDK(_ sdk: MentraBluetoothSDK, didChangeDefaultDevice device: Device?)
|
|
97
|
+
func mentraBluetoothSDK(_ sdk: MentraBluetoothSDK, didLog message: String)
|
|
98
|
+
func mentraBluetoothSDK(_ sdk: MentraBluetoothSDK, didFail error: BluetoothError)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
@MainActor
|
|
102
|
+
public extension MentraBluetoothSDKDelegate {
|
|
103
|
+
func mentraBluetoothSDK(_: MentraBluetoothSDK, didUpdate _: MentraBluetoothState) {}
|
|
104
|
+
func mentraBluetoothSDK(_: MentraBluetoothSDK, didUpdateGlasses _: GlassesRuntimeState) {}
|
|
105
|
+
func mentraBluetoothSDK(_: MentraBluetoothSDK, didUpdateSdkState _: PhoneSdkRuntimeState) {}
|
|
106
|
+
func mentraBluetoothSDK(_: MentraBluetoothSDK, didUpdateScan _: BluetoothScanState) {}
|
|
107
|
+
func mentraBluetoothSDK(_: MentraBluetoothSDK, didDiscover _: Device) {}
|
|
108
|
+
func mentraBluetoothSDK(_: MentraBluetoothSDK, didStopScan _: ScanStopReason) {}
|
|
109
|
+
func mentraBluetoothSDK(_: MentraBluetoothSDK, didReceive _: BluetoothEvent) {}
|
|
110
|
+
func mentraBluetoothSDK(_: MentraBluetoothSDK, didReceiveMicPcm _: MicPcmEvent) {}
|
|
111
|
+
func mentraBluetoothSDK(_: MentraBluetoothSDK, didReceiveMicLc3 _: MicLc3Event) {}
|
|
112
|
+
func mentraBluetoothSDK(_: MentraBluetoothSDK, didChangeDefaultDevice _: Device?) {}
|
|
113
|
+
func mentraBluetoothSDK(_: MentraBluetoothSDK, didLog _: String) {}
|
|
114
|
+
func mentraBluetoothSDK(_: MentraBluetoothSDK, didFail _: BluetoothError) {}
|
|
115
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import CoreBluetooth
|
|
2
|
+
import Foundation
|
|
3
|
+
|
|
4
|
+
final class BluetoothAvailability: NSObject, CBCentralManagerDelegate {
|
|
5
|
+
static let shared = BluetoothAvailability()
|
|
6
|
+
|
|
7
|
+
private var centralManager: CBCentralManager?
|
|
8
|
+
private var state: CBManagerState = .unknown
|
|
9
|
+
|
|
10
|
+
override private init() {
|
|
11
|
+
super.init()
|
|
12
|
+
centralManager = CBCentralManager(
|
|
13
|
+
delegate: self,
|
|
14
|
+
queue: .main,
|
|
15
|
+
options: [CBCentralManagerOptionShowPowerAlertKey: false]
|
|
16
|
+
)
|
|
17
|
+
state = centralManager?.state ?? .unknown
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
func centralManagerDidUpdateState(_ central: CBCentralManager) {
|
|
21
|
+
state = central.state
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
func requirePoweredOn(operation: String) throws {
|
|
25
|
+
if let current = centralManager?.state {
|
|
26
|
+
state = current
|
|
27
|
+
}
|
|
28
|
+
switch state {
|
|
29
|
+
case .poweredOn:
|
|
30
|
+
return
|
|
31
|
+
case .poweredOff:
|
|
32
|
+
throw BluetoothError(
|
|
33
|
+
code: "bluetooth_powered_off",
|
|
34
|
+
message: "Turn on phone Bluetooth to \(operation)."
|
|
35
|
+
)
|
|
36
|
+
case .unauthorized:
|
|
37
|
+
throw BluetoothError(
|
|
38
|
+
code: "bluetooth_unauthorized",
|
|
39
|
+
message: "Allow Bluetooth access to \(operation)."
|
|
40
|
+
)
|
|
41
|
+
case .unsupported:
|
|
42
|
+
throw BluetoothError(
|
|
43
|
+
code: "bluetooth_unsupported",
|
|
44
|
+
message: "This phone does not support Bluetooth."
|
|
45
|
+
)
|
|
46
|
+
case .resetting, .unknown:
|
|
47
|
+
throw BluetoothError(
|
|
48
|
+
code: "bluetooth_not_ready",
|
|
49
|
+
message: "Bluetooth is not ready yet. Try again."
|
|
50
|
+
)
|
|
51
|
+
@unknown default:
|
|
52
|
+
throw BluetoothError(
|
|
53
|
+
code: "bluetooth_unavailable",
|
|
54
|
+
message: "Bluetooth is unavailable. Try again."
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
func intValue(_ value: Any?) -> Int? {
|
|
4
|
+
if let int = value as? Int { return int }
|
|
5
|
+
if let double = value as? Double { return Int(double) }
|
|
6
|
+
if let number = value as? NSNumber { return number.intValue }
|
|
7
|
+
return nil
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
func stringValue(_ values: [String: Any], _ keys: String...) -> String? {
|
|
11
|
+
stringValue(values, keys)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
func stringValue(_ values: [String: Any], _ keys: [String]) -> String? {
|
|
15
|
+
for key in keys {
|
|
16
|
+
if let value = values[key] as? String {
|
|
17
|
+
return value
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return nil
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
func boolValue(_ values: [String: Any], _ keys: String...) -> Bool? {
|
|
24
|
+
boolValue(values, keys)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
func boolValue(_ values: [String: Any], _ keys: [String]) -> Bool? {
|
|
28
|
+
for key in keys {
|
|
29
|
+
if let value = values[key] as? Bool { return value }
|
|
30
|
+
if let value = values[key] as? NSNumber { return value.boolValue }
|
|
31
|
+
}
|
|
32
|
+
return nil
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
func hasAnyKey(_ values: [String: Any], _ keys: String...) -> Bool {
|
|
36
|
+
hasAnyKey(values, keys)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
func hasAnyKey(_ values: [String: Any], _ keys: [String]) -> Bool {
|
|
40
|
+
keys.contains { values.keys.contains($0) }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
func optionalStringValue(_ values: [String: Any], _ keys: String...) -> String? {
|
|
44
|
+
hasAnyKey(values, keys) ? stringValue(values, keys) : nil
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
func nonEmptyStringValue(_ values: [String: Any], _ keys: String...) -> String? {
|
|
48
|
+
for key in keys {
|
|
49
|
+
guard let value = values[key] as? String else { continue }
|
|
50
|
+
let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
51
|
+
if !trimmed.isEmpty {
|
|
52
|
+
return value
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return nil
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
func optionalIntValue(_ values: [String: Any], _ keys: String...) -> Int? {
|
|
59
|
+
guard hasAnyKey(values, keys) else { return nil }
|
|
60
|
+
for key in keys {
|
|
61
|
+
if let value = intValue(values[key]) { return value }
|
|
62
|
+
}
|
|
63
|
+
return nil
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
func optionalBoolValue(_ values: [String: Any], _ keys: String...) -> Bool? {
|
|
67
|
+
hasAnyKey(values, keys) ? boolValue(values, keys) : nil
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
func stringListValue(_ values: [String: Any], _ key: String) -> [String] {
|
|
71
|
+
values[key] as? [String] ?? []
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
func optionalStringListValue(_ values: [String: Any], _ key: String) -> [String]? {
|
|
75
|
+
values.keys.contains(key) ? stringListValue(values, key) : nil
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
func dictionaryListValue(_ values: [String: Any], _ key: String) -> [[String: Any]] {
|
|
79
|
+
values[key] as? [[String: Any]] ?? []
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
func optionalDictionaryListValue(_ values: [String: Any], _ key: String) -> [[String: Any]]? {
|
|
83
|
+
values.keys.contains(key) ? dictionaryListValue(values, key) : nil
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
func putIfNotNil(_ map: inout [String: Any], _ key: String, _ value: Any?) {
|
|
87
|
+
if let value {
|
|
88
|
+
map[key] = value
|
|
89
|
+
}
|
|
90
|
+
}
|