@mentra/bluetooth-sdk 0.1.18 → 0.1.20

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 (77) hide show
  1. package/README.md +1 -1
  2. package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +135 -51
  3. package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +120 -17
  4. package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +5 -6
  5. package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +34 -21
  6. package/android/src/main/java/com/mentra/bluetoothsdk/OtaManifest.kt +1 -1
  7. package/android/src/main/java/com/mentra/bluetoothsdk/camera/CameraModels.kt +13 -0
  8. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G1.kt +1 -1
  9. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G1TextSanitizer.kt +38 -0
  10. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G2.kt +488 -15
  11. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.kt +551 -159
  12. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLiveL2capChannel.kt +274 -0
  13. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraNex.kt +361 -67
  14. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraosBle.java +7501 -3704
  15. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/SGCManager.kt +134 -0
  16. package/android/src/main/java/com/mentra/bluetoothsdk/status/DeviceStatus.kt +0 -6
  17. package/android/src/main/java/com/mentra/bluetoothsdk/types/DeviceModels.kt +2 -3
  18. package/android/src/main/java/com/mentra/bluetoothsdk/utils/BleJsonCompact.java +493 -0
  19. package/android/src/main/java/com/mentra/bluetoothsdk/utils/BlePhotoUploadService.java +65 -5
  20. package/android/src/main/java/com/mentra/bluetoothsdk/utils/BleWireProtocol.java +108 -0
  21. package/android/src/main/java/com/mentra/bluetoothsdk/utils/IncidentLogBleUploadService.java +4 -3
  22. package/android/src/main/java/com/mentra/bluetoothsdk/utils/K900LengthCodec.java +115 -0
  23. package/android/src/main/java/com/mentra/bluetoothsdk/utils/K900ProtocolUtils.java +109 -73
  24. package/android/src/main/java/com/mentra/bluetoothsdk/utils/MessageChunkReassembler.java +106 -0
  25. package/android/src/main/java/com/mentra/bluetoothsdk/utils/MessageChunker.java +93 -0
  26. package/android/src/main/java/com/mentra/bluetoothsdk/utils/NexSGCUtils.kt +111 -11
  27. package/android/src/test/java/com/mentra/bluetoothsdk/BluetoothSdkExceptionTest.kt +18 -0
  28. package/android/src/test/java/com/mentra/bluetoothsdk/camera/PhotoRequestTest.kt +23 -9
  29. package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/G1TextSanitizerTest.kt +31 -0
  30. package/android/src/test/java/com/mentra/bluetoothsdk/utils/AvifExifStripperTest.java +17 -1
  31. package/android/src/test/java/com/mentra/bluetoothsdk/utils/BinaryMessageChunkerTest.java +84 -0
  32. package/android/src/test/java/com/mentra/bluetoothsdk/utils/BleJsonCompactTest.java +158 -0
  33. package/android/src/test/java/com/mentra/bluetoothsdk/utils/BlePhotoUploadServiceTest.java +9 -0
  34. package/android/src/test/java/com/mentra/bluetoothsdk/utils/K900ProtocolUtilsEndiannessTest.java +172 -0
  35. package/android/src/test/java/com/mentra/bluetoothsdk/utils/K900ProtocolUtilsTest.java +24 -0
  36. package/build/BluetoothSdk.types.d.ts +37 -1
  37. package/build/BluetoothSdk.types.d.ts.map +1 -1
  38. package/build/BluetoothSdk.types.js.map +1 -1
  39. package/build/_private/BluetoothSdkModule.d.ts +4 -0
  40. package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
  41. package/build/_private/BluetoothSdkModule.js.map +1 -1
  42. package/build/_private/photoRequestPayload.d.ts.map +1 -1
  43. package/build/_private/photoRequestPayload.js +1 -0
  44. package/build/_private/photoRequestPayload.js.map +1 -1
  45. package/build/index.d.ts +1 -1
  46. package/build/index.d.ts.map +1 -1
  47. package/build/index.js +5 -0
  48. package/build/index.js.map +1 -1
  49. package/build/types/index.d.ts +3 -0
  50. package/build/types/index.d.ts.map +1 -0
  51. package/build/types/index.js +2 -0
  52. package/build/types/index.js.map +1 -0
  53. package/ios/Source/BluetoothSdkDefaults.swift +1 -1
  54. package/ios/Source/DeviceManager.swift +136 -30
  55. package/ios/Source/DeviceStore.swift +5 -5
  56. package/ios/Source/MentraBluetoothSDK.swift +40 -24
  57. package/ios/Source/OtaManifest.swift +1 -1
  58. package/ios/Source/camera/CameraModels.swift +17 -3
  59. package/ios/Source/sgcs/G1.swift +1 -1
  60. package/ios/Source/sgcs/G2.swift +518 -11
  61. package/ios/Source/sgcs/MentraLive.swift +443 -42
  62. package/ios/Source/sgcs/MentraLiveL2capChannel.swift +183 -0
  63. package/ios/Source/sgcs/MentraNex.swift +416 -32
  64. package/ios/Source/sgcs/SGCManager.swift +155 -0
  65. package/ios/Source/sgcs/mentraos_ble.pb.swift +726 -122
  66. package/ios/Source/status/DeviceStatus.swift +0 -8
  67. package/ios/Source/utils/BleJsonCompact.swift +395 -0
  68. package/ios/Source/utils/BleWireProtocol.swift +92 -0
  69. package/ios/Source/utils/G1Text.swift +30 -1
  70. package/ios/Source/utils/MessageChunkReassembler.swift +82 -0
  71. package/ios/Source/utils/MessageChunker.swift +73 -0
  72. package/package.json +14 -7
  73. package/src/BluetoothSdk.types.ts +39 -1
  74. package/src/_private/BluetoothSdkModule.ts +4 -0
  75. package/src/_private/photoRequestPayload.ts +1 -0
  76. package/src/index.ts +6 -0
  77. package/src/types/index.ts +7 -0
@@ -6,6 +6,39 @@ import com.mentra.bluetoothsdk.DeviceStore
6
6
  import com.mentra.bluetoothsdk.PhotoRequest
7
7
  import com.mentra.bluetoothsdk.utils.ConnTypes
8
8
 
9
+ /**
10
+ * One element of a scene frame (display.render()). Geometry is raw pixels on
11
+ * the device's drawable canvas; `change` is the host differ's annotation
12
+ * against the previous frame sent to this device.
13
+ */
14
+ data class SceneElement(
15
+ val id: String,
16
+ val type: String, // "text" | "rect" | "image"
17
+ val x: Int,
18
+ val y: Int,
19
+ val w: Int,
20
+ val h: Int,
21
+ val text: String?,
22
+ val data: String?, // base64 image pixels (SGC decodes → re-encodes to wire format)
23
+ val border: Int,
24
+ val radius: Int,
25
+ val change: String, // "created" | "updated" | "moved" | "unchanged"
26
+ val contentHash: String
27
+ )
28
+
29
+ /**
30
+ * A whole scene from the host pipeline — the full frame plus per-element change
31
+ * annotations and host-computed removes. Full-frame consumers can serialize
32
+ * `elements` and ignore the annotations; per-element consumers walk them.
33
+ */
34
+ data class SceneFrame(
35
+ val appId: String,
36
+ val epoch: Int,
37
+ val replay: Boolean,
38
+ val elements: List<SceneElement>,
39
+ val removed: List<String>
40
+ )
41
+
9
42
  abstract class SGCManager {
10
43
  // Hard coded device properties:
11
44
  @JvmField var type: String = ""
@@ -91,6 +124,107 @@ abstract class SGCManager {
91
124
  borderWidth: Int = 0,
92
125
  borderRadius: Int = 0
93
126
  ) {}
127
+
128
+ /**
129
+ * Retained-mode layout element (bitmap). Like [displayBitmap] but tagged with a stable
130
+ * [elementId] within a [layoutId] scene, so an SGC that supports it (Nex) can diff:
131
+ * same id -> update in place, new id -> create, new layoutId -> clear the previous scene.
132
+ * Default: ignore the ids and behave exactly like [displayBitmap].
133
+ */
134
+ open fun drawLayoutBitmap(
135
+ base64ImageData: String,
136
+ x: Int,
137
+ y: Int,
138
+ width: Int,
139
+ height: Int,
140
+ elementId: String,
141
+ layoutId: String?
142
+ ): Boolean = displayBitmap(base64ImageData, x, y, width, height)
143
+
144
+ /** Retained-mode layout element (text). Default: behave like [sendPositionedText]. */
145
+ open fun drawLayoutText(
146
+ text: String,
147
+ x: Int,
148
+ y: Int,
149
+ width: Int,
150
+ height: Int,
151
+ borderWidth: Int,
152
+ borderRadius: Int,
153
+ elementId: String,
154
+ layoutId: String?
155
+ ) = sendPositionedText(text, x, y, width, height, borderWidth, borderRadius)
156
+
157
+ /** Remove a retained layout element by id. Default: no-op (SGC doesn't support layouts). */
158
+ open fun removeLayoutElement(elementId: String, layoutId: String?) {}
159
+
160
+ /**
161
+ * Apply a whole scene frame from the host pipeline (display.render()).
162
+ *
163
+ * The host has already diffed the scene against what it last sent, so each
164
+ * element arrives annotated (created/updated/moved/unchanged) and `removed`
165
+ * lists what disappeared. This default implementation is fully generic:
166
+ *
167
+ * - PAINT-THEN-SWEEP: creates/updates/moves first (new content lands over
168
+ * or beside old), removes LAST — so a scene transition never shows a
169
+ * blank interval.
170
+ * - "unchanged" elements are skipped entirely (the host diff is the truth;
171
+ * this is also what prevents image re-uploads over BLE).
172
+ * - `replay` frames arrive all-"created" after a reconnect/recovery —
173
+ * [onSceneReplay] lets an SGC reset its component registry first so
174
+ * creates take the create path (updates to dead firmware ids are dropped
175
+ * silently by hardware).
176
+ * - rects compile to empty bordered text boxes (no shape primitive on
177
+ * current targets); a rect always gets a visible border.
178
+ *
179
+ * SGCs with per-element verbs (G2, Mentra Display) just implement
180
+ * [drawLayoutText]/[drawLayoutBitmap]/[removeLayoutElement]. A full-frame
181
+ * device (e.g. NIMO-style) overrides this whole method instead and
182
+ * serializes [SceneFrame.elements], ignoring the annotations.
183
+ */
184
+ open fun applySceneFrame(frame: SceneFrame) {
185
+ if (frame.replay) {
186
+ onSceneReplay(frame.appId)
187
+ }
188
+ // An id in `removed` that ALSO appears in `elements` is a type change
189
+ // (the differ keys matches by type:id). Its removal must run BEFORE the
190
+ // paint — registries key by id, so a post-paint sweep would delete the
191
+ // just-painted replacement.
192
+ val paintedIds = frame.elements.mapTo(HashSet()) { it.id }
193
+ for (id in frame.removed) {
194
+ if (id in paintedIds) removeLayoutElement(id, frame.appId)
195
+ }
196
+ for (el in frame.elements) {
197
+ if (!frame.replay && el.change == "unchanged") continue
198
+ when (el.type) {
199
+ "text" ->
200
+ drawLayoutText(el.text ?: "", el.x, el.y, el.w, el.h, el.border, el.radius, el.id, frame.appId)
201
+ "rect" ->
202
+ drawLayoutText("", el.x, el.y, el.w, el.h, maxOf(1, el.border), el.radius, el.id, frame.appId)
203
+ "image" ->
204
+ el.data?.let { drawLayoutBitmap(it, el.x, el.y, el.w, el.h, el.id, frame.appId) }
205
+ else -> Bridge.log("SGC: applySceneFrame: unknown element type ${el.type}")
206
+ }
207
+ }
208
+ for (id in frame.removed) {
209
+ if (id !in paintedIds) removeLayoutElement(id, frame.appId)
210
+ }
211
+ }
212
+
213
+ /**
214
+ * A replay frame is about to repaint from scratch — forget any retained
215
+ * component bookkeeping for [appId] so "created" elements take the create
216
+ * path. Default: no-op.
217
+ */
218
+ open fun onSceneReplay(appId: String) {}
219
+
220
+ /**
221
+ * Remove a set of scene elements (scene→legacy handoff: a legacy layout is
222
+ * about to draw and the previous scene's elements must not linger under it).
223
+ */
224
+ open fun clearSceneElements(elementIds: List<String>) {
225
+ for (id in elementIds) removeLayoutElement(id, null)
226
+ }
227
+
94
228
  abstract fun showDashboard()
95
229
  abstract fun setDashboardPosition(height: Int, depth: Int)
96
230
 
@@ -255,7 +255,6 @@ internal data class BluetoothStatus(
255
255
  val shouldSendPcm: Boolean,
256
256
  val shouldSendLc3: Boolean,
257
257
  val shouldSendTranscript: Boolean,
258
- val offlineCaptionsRunning: Boolean,
259
258
  val localSttFallbackActive: Boolean,
260
259
  val shouldSendBootingMessage: Boolean,
261
260
  ) {
@@ -307,7 +306,6 @@ internal data class BluetoothStatus(
307
306
  "should_send_pcm" to shouldSendPcm,
308
307
  "should_send_lc3" to shouldSendLc3,
309
308
  "should_send_transcript" to shouldSendTranscript,
310
- "offline_captions_running" to offlineCaptionsRunning,
311
309
  "local_stt_fallback_active" to localSttFallbackActive,
312
310
  "shouldSendBootingMessage" to shouldSendBootingMessage,
313
311
  )
@@ -354,7 +352,6 @@ internal data class BluetoothStatus(
354
352
  shouldSendPcm = boolValue(values, "should_send_pcm") ?: false,
355
353
  shouldSendLc3 = boolValue(values, "should_send_lc3") ?: false,
356
354
  shouldSendTranscript = boolValue(values, "should_send_transcript") ?: false,
357
- offlineCaptionsRunning = boolValue(values, "offline_captions_running") ?: false,
358
355
  localSttFallbackActive = boolValue(values, "local_stt_fallback_active") ?: false,
359
356
  shouldSendBootingMessage = boolValue(values, "shouldSendBootingMessage") ?: true,
360
357
  )
@@ -558,7 +555,6 @@ internal data class BluetoothStatusUpdate(
558
555
  val shouldSendPcm: Boolean? = null,
559
556
  val shouldSendLc3: Boolean? = null,
560
557
  val shouldSendTranscript: Boolean? = null,
561
- val offlineCaptionsRunning: Boolean? = null,
562
558
  val localSttFallbackActive: Boolean? = null,
563
559
  val shouldSendBootingMessage: Boolean? = null,
564
560
  ) {
@@ -600,7 +596,6 @@ internal data class BluetoothStatusUpdate(
600
596
  putIfNotNull("should_send_pcm", shouldSendPcm)
601
597
  putIfNotNull("should_send_lc3", shouldSendLc3)
602
598
  putIfNotNull("should_send_transcript", shouldSendTranscript)
603
- putIfNotNull("offline_captions_running", offlineCaptionsRunning)
604
599
  putIfNotNull("local_stt_fallback_active", localSttFallbackActive)
605
600
  putIfNotNull("shouldSendBootingMessage", shouldSendBootingMessage)
606
601
  }
@@ -650,7 +645,6 @@ internal data class BluetoothStatusUpdate(
650
645
  shouldSendPcm = optionalBoolValue(values, "should_send_pcm"),
651
646
  shouldSendLc3 = optionalBoolValue(values, "should_send_lc3"),
652
647
  shouldSendTranscript = optionalBoolValue(values, "should_send_transcript"),
653
- offlineCaptionsRunning = optionalBoolValue(values, "offline_captions_running"),
654
648
  localSttFallbackActive = optionalBoolValue(values, "local_stt_fallback_active"),
655
649
  shouldSendBootingMessage = optionalBoolValue(values, "shouldSendBootingMessage"),
656
650
  )
@@ -2,7 +2,6 @@ package com.mentra.bluetoothsdk
2
2
 
3
3
  import com.mentra.bluetoothsdk.utils.ControllerTypes
4
4
  import com.mentra.bluetoothsdk.utils.DeviceTypes
5
- import expo.modules.kotlin.exception.CodedException
6
5
 
7
6
  data class MentraBluetoothSdkConfig @JvmOverloads constructor(
8
7
  val deliverCallbacksOnMainThread: Boolean = true,
@@ -10,10 +9,10 @@ data class MentraBluetoothSdkConfig @JvmOverloads constructor(
10
9
  )
11
10
 
12
11
  class BluetoothSdkException(
13
- code: String,
12
+ val code: String,
14
13
  message: String,
15
14
  cause: Throwable? = null,
16
- ) : CodedException(code, message, cause)
15
+ ) : IllegalStateException(message, cause)
17
16
 
18
17
  enum class DeviceModel(val deviceType: String) {
19
18
  G1(DeviceTypes.G1),