@mentra/bluetooth-sdk 0.1.18 → 0.1.19
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 +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +119 -16
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +0 -6
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +22 -21
- package/android/src/main/java/com/mentra/bluetoothsdk/OtaManifest.kt +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G2.kt +488 -15
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.kt +457 -31
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLiveL2capChannel.kt +274 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraNex.kt +361 -67
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraosBle.java +7501 -3704
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/SGCManager.kt +134 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/status/DeviceStatus.kt +0 -6
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/BleJsonCompact.java +493 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/BlePhotoUploadService.java +4 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/BleWireProtocol.java +108 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/IncidentLogBleUploadService.java +4 -3
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/K900LengthCodec.java +115 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/K900ProtocolUtils.java +103 -68
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/MessageChunkReassembler.java +106 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/MessageChunker.java +93 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/NexSGCUtils.kt +111 -11
- package/android/src/test/java/com/mentra/bluetoothsdk/camera/PhotoRequestTest.kt +7 -9
- package/android/src/test/java/com/mentra/bluetoothsdk/utils/AvifExifStripperTest.java +17 -1
- package/android/src/test/java/com/mentra/bluetoothsdk/utils/BinaryMessageChunkerTest.java +84 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/utils/BleJsonCompactTest.java +158 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/utils/K900ProtocolUtilsEndiannessTest.java +172 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/utils/K900ProtocolUtilsTest.java +24 -0
- package/build/BluetoothSdk.types.d.ts +12 -1
- package/build/BluetoothSdk.types.d.ts.map +1 -1
- package/build/BluetoothSdk.types.js.map +1 -1
- package/build/types/index.d.ts +3 -0
- package/build/types/index.d.ts.map +1 -0
- package/build/types/index.js +2 -0
- package/build/types/index.js.map +1 -0
- package/ios/Source/BluetoothSdkDefaults.swift +1 -1
- package/ios/Source/DeviceManager.swift +134 -29
- package/ios/Source/DeviceStore.swift +0 -5
- package/ios/Source/MentraBluetoothSDK.swift +26 -23
- package/ios/Source/OtaManifest.swift +1 -1
- package/ios/Source/sgcs/G2.swift +518 -11
- package/ios/Source/sgcs/MentraLive.swift +308 -30
- package/ios/Source/sgcs/MentraNex.swift +416 -32
- package/ios/Source/sgcs/SGCManager.swift +155 -0
- package/ios/Source/sgcs/mentraos_ble.pb.swift +726 -122
- package/ios/Source/status/DeviceStatus.swift +0 -8
- package/ios/Source/utils/BleJsonCompact.swift +395 -0
- package/ios/Source/utils/BleWireProtocol.swift +92 -0
- package/ios/Source/utils/MessageChunkReassembler.swift +82 -0
- package/ios/Source/utils/MessageChunker.swift +73 -0
- package/package.json +14 -7
- package/src/BluetoothSdk.types.ts +13 -1
- package/src/types/index.ts +7 -0
|
@@ -1,5 +1,49 @@
|
|
|
1
1
|
import Foundation
|
|
2
2
|
|
|
3
|
+
/// One element of a scene frame (display.render()). Geometry is raw pixels on
|
|
4
|
+
/// the device's drawable canvas; `change` is the host differ's annotation
|
|
5
|
+
/// against the previous frame sent to this device.
|
|
6
|
+
struct SceneElement {
|
|
7
|
+
let id: String
|
|
8
|
+
let type: String // "text" | "rect" | "image"
|
|
9
|
+
let x: Int32
|
|
10
|
+
let y: Int32
|
|
11
|
+
let w: Int32
|
|
12
|
+
let h: Int32
|
|
13
|
+
let text: String?
|
|
14
|
+
let data: String? // base64 image pixels (SGC decodes → re-encodes to wire format)
|
|
15
|
+
let border: Int32
|
|
16
|
+
let radius: Int32
|
|
17
|
+
let change: String // "created" | "updated" | "moved" | "unchanged"
|
|
18
|
+
let contentHash: String
|
|
19
|
+
|
|
20
|
+
func with(change: String) -> SceneElement {
|
|
21
|
+
SceneElement(
|
|
22
|
+
id: id, type: type, x: x, y: y, w: w, h: h, text: text, data: data,
|
|
23
|
+
border: border, radius: radius, change: change, contentHash: contentHash
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/// A whole scene from the host pipeline — the full frame plus per-element
|
|
29
|
+
/// change annotations and host-computed removes. Full-frame consumers can
|
|
30
|
+
/// serialize `elements` and ignore the annotations; per-element consumers walk
|
|
31
|
+
/// them.
|
|
32
|
+
struct SceneFrame {
|
|
33
|
+
let appId: String
|
|
34
|
+
let epoch: Int
|
|
35
|
+
let replay: Bool
|
|
36
|
+
let elements: [SceneElement]
|
|
37
|
+
let removed: [String]
|
|
38
|
+
|
|
39
|
+
func asReplay() -> SceneFrame {
|
|
40
|
+
SceneFrame(
|
|
41
|
+
appId: appId, epoch: epoch, replay: true,
|
|
42
|
+
elements: elements.map { $0.with(change: "created") }, removed: removed
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
3
47
|
@MainActor
|
|
4
48
|
protocol SGCManager {
|
|
5
49
|
// MARK: - hard coded device properties:
|
|
@@ -62,6 +106,29 @@ protocol SGCManager {
|
|
|
62
106
|
_ text: String, x: Int32, y: Int32, width: Int32, height: Int32,
|
|
63
107
|
borderWidth: Int32, borderRadius: Int32
|
|
64
108
|
) async
|
|
109
|
+
|
|
110
|
+
// MARK: - Scene display (display.render() pipeline)
|
|
111
|
+
|
|
112
|
+
/// Retained element verbs — identity is the element id. Defaults delegate
|
|
113
|
+
/// to the positioned/bitmap paths (see extension); SGCs with retained
|
|
114
|
+
/// components (G2 containers, Mentra Display canvas) override for
|
|
115
|
+
/// update-in-place / delete semantics.
|
|
116
|
+
func drawLayoutText(
|
|
117
|
+
_ text: String, x: Int32, y: Int32, width: Int32, height: Int32,
|
|
118
|
+
borderWidth: Int32, borderRadius: Int32, elementId: String, layoutId: String?
|
|
119
|
+
) async
|
|
120
|
+
func drawLayoutBitmap(
|
|
121
|
+
base64ImageData: String, x: Int32, y: Int32, width: Int32, height: Int32,
|
|
122
|
+
elementId: String, layoutId: String?
|
|
123
|
+
) async -> Bool
|
|
124
|
+
func removeLayoutElement(_ elementId: String, layoutId: String?) async
|
|
125
|
+
/// Apply a whole host-diffed scene frame (default: paint-then-sweep over the verbs above).
|
|
126
|
+
func applySceneFrame(_ frame: SceneFrame) async
|
|
127
|
+
/// A replay frame is about to repaint from scratch — reset retained bookkeeping.
|
|
128
|
+
func onSceneReplay(_ appId: String) async
|
|
129
|
+
/// Remove a set of scene elements (scene→legacy handoff sweep).
|
|
130
|
+
func clearSceneElements(_ elementIds: [String]) async
|
|
131
|
+
|
|
65
132
|
func showDashboard()
|
|
66
133
|
func setDashboardPosition(_ height: Int, _ depth: Int)
|
|
67
134
|
/// Default implementation sends both via [setDashboardPosition]; Nex overrides to one protobuf.
|
|
@@ -156,6 +223,94 @@ extension SGCManager {
|
|
|
156
223
|
borderWidth _: Int32, borderRadius _: Int32
|
|
157
224
|
) async {}
|
|
158
225
|
|
|
226
|
+
// MARK: - Scene display defaults
|
|
227
|
+
|
|
228
|
+
/// Default: ignore the ids and behave like the positioned-text path.
|
|
229
|
+
func drawLayoutText(
|
|
230
|
+
_ text: String, x: Int32, y: Int32, width: Int32, height: Int32,
|
|
231
|
+
borderWidth: Int32, borderRadius: Int32, elementId _: String, layoutId _: String?
|
|
232
|
+
) async {
|
|
233
|
+
await sendPositionedText(
|
|
234
|
+
text, x: x, y: y, width: width, height: height,
|
|
235
|
+
borderWidth: borderWidth, borderRadius: borderRadius
|
|
236
|
+
)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/// Default: ignore the ids and behave like displayBitmap.
|
|
240
|
+
func drawLayoutBitmap(
|
|
241
|
+
base64ImageData: String, x: Int32, y: Int32, width: Int32, height: Int32,
|
|
242
|
+
elementId _: String, layoutId _: String?
|
|
243
|
+
) async -> Bool {
|
|
244
|
+
await displayBitmap(base64ImageData: base64ImageData, x: x, y: y, width: width, height: height)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/// Default: no-op (SGC has no retained elements).
|
|
248
|
+
func removeLayoutElement(_: String, layoutId _: String?) async {}
|
|
249
|
+
|
|
250
|
+
/// Default: no retained bookkeeping to reset.
|
|
251
|
+
func onSceneReplay(_: String) async {}
|
|
252
|
+
|
|
253
|
+
/// Default sweep: remove each element individually.
|
|
254
|
+
func clearSceneElements(_ elementIds: [String]) async {
|
|
255
|
+
for id in elementIds {
|
|
256
|
+
await removeLayoutElement(id, layoutId: nil)
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/// Apply a whole scene frame from the host pipeline (display.render()).
|
|
261
|
+
///
|
|
262
|
+
/// The host has already diffed the scene, so each element arrives annotated
|
|
263
|
+
/// and `removed` lists what disappeared. PAINT-THEN-SWEEP: creates/updates/
|
|
264
|
+
/// moves land first, removes go LAST — a scene transition never shows a
|
|
265
|
+
/// blank interval. "unchanged" elements are skipped entirely (this is also
|
|
266
|
+
/// what prevents image re-uploads over BLE). Replay frames arrive
|
|
267
|
+
/// all-"created" and reset retained bookkeeping first, because firmware
|
|
268
|
+
/// silently drops updates to dead component ids. Rects compile to empty
|
|
269
|
+
/// bordered text boxes (no shape primitive on current targets) and always
|
|
270
|
+
/// get a visible border.
|
|
271
|
+
func applySceneFrame(_ frame: SceneFrame) async {
|
|
272
|
+
if frame.replay {
|
|
273
|
+
await onSceneReplay(frame.appId)
|
|
274
|
+
}
|
|
275
|
+
// An id in `removed` that ALSO appears in `elements` is a type change
|
|
276
|
+
// (the differ keys matches by type:id). Its removal must run BEFORE the
|
|
277
|
+
// paint — registries key by id, so a post-paint sweep would delete the
|
|
278
|
+
// just-painted replacement.
|
|
279
|
+
let paintedIds = Set(frame.elements.map { $0.id })
|
|
280
|
+
for id in frame.removed where paintedIds.contains(id) {
|
|
281
|
+
await removeLayoutElement(id, layoutId: frame.appId)
|
|
282
|
+
}
|
|
283
|
+
for el in frame.elements {
|
|
284
|
+
if !frame.replay, el.change == "unchanged" { continue }
|
|
285
|
+
switch el.type {
|
|
286
|
+
case "text":
|
|
287
|
+
await drawLayoutText(
|
|
288
|
+
el.text ?? "", x: el.x, y: el.y, width: el.w, height: el.h,
|
|
289
|
+
borderWidth: el.border, borderRadius: el.radius,
|
|
290
|
+
elementId: el.id, layoutId: frame.appId
|
|
291
|
+
)
|
|
292
|
+
case "rect":
|
|
293
|
+
await drawLayoutText(
|
|
294
|
+
"", x: el.x, y: el.y, width: el.w, height: el.h,
|
|
295
|
+
borderWidth: max(1, el.border), borderRadius: el.radius,
|
|
296
|
+
elementId: el.id, layoutId: frame.appId
|
|
297
|
+
)
|
|
298
|
+
case "image":
|
|
299
|
+
if let data = el.data {
|
|
300
|
+
_ = await drawLayoutBitmap(
|
|
301
|
+
base64ImageData: data, x: el.x, y: el.y, width: el.w, height: el.h,
|
|
302
|
+
elementId: el.id, layoutId: frame.appId
|
|
303
|
+
)
|
|
304
|
+
}
|
|
305
|
+
default:
|
|
306
|
+
Bridge.log("SGC: applySceneFrame: unknown element type \(el.type)")
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
for id in frame.removed where !paintedIds.contains(id) {
|
|
310
|
+
await removeLayoutElement(id, layoutId: frame.appId)
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
159
314
|
// MARK: - Video recording (default: ignore custom settings, use saved defaults)
|
|
160
315
|
|
|
161
316
|
func startVideoRecording(
|