@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.
Files changed (52) hide show
  1. package/README.md +1 -1
  2. package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +119 -16
  3. package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +0 -6
  4. package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +22 -21
  5. package/android/src/main/java/com/mentra/bluetoothsdk/OtaManifest.kt +1 -1
  6. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G2.kt +488 -15
  7. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.kt +457 -31
  8. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLiveL2capChannel.kt +274 -0
  9. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraNex.kt +361 -67
  10. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraosBle.java +7501 -3704
  11. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/SGCManager.kt +134 -0
  12. package/android/src/main/java/com/mentra/bluetoothsdk/status/DeviceStatus.kt +0 -6
  13. package/android/src/main/java/com/mentra/bluetoothsdk/utils/BleJsonCompact.java +493 -0
  14. package/android/src/main/java/com/mentra/bluetoothsdk/utils/BlePhotoUploadService.java +4 -1
  15. package/android/src/main/java/com/mentra/bluetoothsdk/utils/BleWireProtocol.java +108 -0
  16. package/android/src/main/java/com/mentra/bluetoothsdk/utils/IncidentLogBleUploadService.java +4 -3
  17. package/android/src/main/java/com/mentra/bluetoothsdk/utils/K900LengthCodec.java +115 -0
  18. package/android/src/main/java/com/mentra/bluetoothsdk/utils/K900ProtocolUtils.java +103 -68
  19. package/android/src/main/java/com/mentra/bluetoothsdk/utils/MessageChunkReassembler.java +106 -0
  20. package/android/src/main/java/com/mentra/bluetoothsdk/utils/MessageChunker.java +93 -0
  21. package/android/src/main/java/com/mentra/bluetoothsdk/utils/NexSGCUtils.kt +111 -11
  22. package/android/src/test/java/com/mentra/bluetoothsdk/camera/PhotoRequestTest.kt +7 -9
  23. package/android/src/test/java/com/mentra/bluetoothsdk/utils/AvifExifStripperTest.java +17 -1
  24. package/android/src/test/java/com/mentra/bluetoothsdk/utils/BinaryMessageChunkerTest.java +84 -0
  25. package/android/src/test/java/com/mentra/bluetoothsdk/utils/BleJsonCompactTest.java +158 -0
  26. package/android/src/test/java/com/mentra/bluetoothsdk/utils/K900ProtocolUtilsEndiannessTest.java +172 -0
  27. package/android/src/test/java/com/mentra/bluetoothsdk/utils/K900ProtocolUtilsTest.java +24 -0
  28. package/build/BluetoothSdk.types.d.ts +12 -1
  29. package/build/BluetoothSdk.types.d.ts.map +1 -1
  30. package/build/BluetoothSdk.types.js.map +1 -1
  31. package/build/types/index.d.ts +3 -0
  32. package/build/types/index.d.ts.map +1 -0
  33. package/build/types/index.js +2 -0
  34. package/build/types/index.js.map +1 -0
  35. package/ios/Source/BluetoothSdkDefaults.swift +1 -1
  36. package/ios/Source/DeviceManager.swift +134 -29
  37. package/ios/Source/DeviceStore.swift +0 -5
  38. package/ios/Source/MentraBluetoothSDK.swift +26 -23
  39. package/ios/Source/OtaManifest.swift +1 -1
  40. package/ios/Source/sgcs/G2.swift +518 -11
  41. package/ios/Source/sgcs/MentraLive.swift +308 -30
  42. package/ios/Source/sgcs/MentraNex.swift +416 -32
  43. package/ios/Source/sgcs/SGCManager.swift +155 -0
  44. package/ios/Source/sgcs/mentraos_ble.pb.swift +726 -122
  45. package/ios/Source/status/DeviceStatus.swift +0 -8
  46. package/ios/Source/utils/BleJsonCompact.swift +395 -0
  47. package/ios/Source/utils/BleWireProtocol.swift +92 -0
  48. package/ios/Source/utils/MessageChunkReassembler.swift +82 -0
  49. package/ios/Source/utils/MessageChunker.swift +73 -0
  50. package/package.json +14 -7
  51. package/src/BluetoothSdk.types.ts +13 -1
  52. 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(