@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.
Files changed (42) hide show
  1. package/README.md +29 -13
  2. package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +15 -7
  3. package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +1 -0
  4. package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +34 -46
  5. package/android/src/main/java/com/mentra/bluetoothsdk/audio/AudioModels.kt +125 -0
  6. package/android/src/main/java/com/mentra/bluetoothsdk/camera/CameraModels.kt +187 -0
  7. package/android/src/main/java/com/mentra/bluetoothsdk/connection/ConnectionModels.kt +78 -0
  8. package/android/src/main/java/com/mentra/bluetoothsdk/events/BluetoothEvents.kt +60 -0
  9. package/android/src/main/java/com/mentra/bluetoothsdk/internal/MapParsing.kt +132 -0
  10. package/android/src/main/java/com/mentra/bluetoothsdk/requests/DisplayRequests.kt +39 -0
  11. package/android/src/main/java/com/mentra/bluetoothsdk/status/DeviceStatus.kt +605 -0
  12. package/android/src/main/java/com/mentra/bluetoothsdk/status/RuntimeState.kt +199 -0
  13. package/android/src/main/java/com/mentra/bluetoothsdk/status/WifiHotspotStatus.kt +173 -0
  14. package/android/src/main/java/com/mentra/bluetoothsdk/streaming/StreamModels.kt +348 -0
  15. package/android/src/main/java/com/mentra/bluetoothsdk/types/DeviceModels.kt +75 -0
  16. package/build/BluetoothSdk.types.d.ts +4 -0
  17. package/build/BluetoothSdk.types.d.ts.map +1 -1
  18. package/build/BluetoothSdk.types.js.map +1 -1
  19. package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
  20. package/build/_private/BluetoothSdkModule.js +3 -0
  21. package/build/_private/BluetoothSdkModule.js.map +1 -1
  22. package/ios/BluetoothSdkModule.swift +4 -4
  23. package/ios/Source/Audio/AudioModels.swift +159 -0
  24. package/ios/Source/Camera/CameraModels.swift +246 -0
  25. package/ios/Source/Connection/ScanSession.swift +27 -0
  26. package/ios/Source/DeviceStore.swift +1 -0
  27. package/ios/Source/Errors/BluetoothError.swift +19 -0
  28. package/ios/Source/Events/BluetoothEvents.swift +115 -0
  29. package/ios/Source/Internal/BluetoothAvailability.swift +58 -0
  30. package/ios/Source/Internal/ValueParsing.swift +90 -0
  31. package/ios/Source/MentraBluetoothSDK.swift +25 -2140
  32. package/ios/Source/Requests/DisplayRequests.swift +58 -0
  33. package/ios/Source/Status/DeviceStatus.swift +463 -0
  34. package/ios/Source/Status/RuntimeState.swift +350 -0
  35. package/ios/Source/Status/WifiHotspotStatus.swift +326 -0
  36. package/ios/Source/Streaming/StreamModels.swift +436 -0
  37. package/ios/Source/Types/DeviceModels.swift +134 -0
  38. package/ios/Source/sgcs/G2.swift +97 -36
  39. package/package.json +1 -1
  40. package/src/BluetoothSdk.types.ts +4 -0
  41. package/src/_private/BluetoothSdkModule.ts +3 -0
  42. 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
+ }