@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.
- package/README.md +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +135 -51
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +120 -17
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +5 -6
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +34 -21
- package/android/src/main/java/com/mentra/bluetoothsdk/OtaManifest.kt +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/camera/CameraModels.kt +13 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G1.kt +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G1TextSanitizer.kt +38 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G2.kt +488 -15
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.kt +551 -159
- 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/types/DeviceModels.kt +2 -3
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/BleJsonCompact.java +493 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/BlePhotoUploadService.java +65 -5
- 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 +109 -73
- 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/BluetoothSdkExceptionTest.kt +18 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/camera/PhotoRequestTest.kt +23 -9
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/G1TextSanitizerTest.kt +31 -0
- 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/BlePhotoUploadServiceTest.java +9 -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 +37 -1
- package/build/BluetoothSdk.types.d.ts.map +1 -1
- package/build/BluetoothSdk.types.js.map +1 -1
- package/build/_private/BluetoothSdkModule.d.ts +4 -0
- package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
- package/build/_private/BluetoothSdkModule.js.map +1 -1
- package/build/_private/photoRequestPayload.d.ts.map +1 -1
- package/build/_private/photoRequestPayload.js +1 -0
- package/build/_private/photoRequestPayload.js.map +1 -1
- package/build/index.d.ts +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +5 -0
- package/build/index.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 +136 -30
- package/ios/Source/DeviceStore.swift +5 -5
- package/ios/Source/MentraBluetoothSDK.swift +40 -24
- package/ios/Source/OtaManifest.swift +1 -1
- package/ios/Source/camera/CameraModels.swift +17 -3
- package/ios/Source/sgcs/G1.swift +1 -1
- package/ios/Source/sgcs/G2.swift +518 -11
- package/ios/Source/sgcs/MentraLive.swift +443 -42
- package/ios/Source/sgcs/MentraLiveL2capChannel.swift +183 -0
- 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/G1Text.swift +30 -1
- 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 +39 -1
- package/src/_private/BluetoothSdkModule.ts +4 -0
- package/src/_private/photoRequestPayload.ts +1 -0
- package/src/index.ts +6 -0
- 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
|
-
) :
|
|
15
|
+
) : IllegalStateException(message, cause)
|
|
17
16
|
|
|
18
17
|
enum class DeviceModel(val deviceType: String) {
|
|
19
18
|
G1(DeviceTypes.G1),
|