@mentra/bluetooth-sdk 0.1.13 → 0.1.16
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 +18 -8
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +48 -48
- package/android/src/main/java/com/mentra/bluetoothsdk/Bridge.kt +5 -20
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +360 -269
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +3 -7
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +152 -72
- package/android/src/main/java/com/mentra/bluetoothsdk/OtaManifest.kt +12 -12
- package/android/src/main/java/com/mentra/bluetoothsdk/camera/CameraModels.kt +25 -11
- package/android/src/main/java/com/mentra/bluetoothsdk/controllers/ControllerManager.kt +1 -2
- package/android/src/main/java/com/mentra/bluetoothsdk/controllers/R1.kt +1 -2
- package/android/src/main/java/com/mentra/bluetoothsdk/events/BluetoothEvents.kt +1 -22
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G1.kt +51 -8
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G2.kt +1184 -800
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Mach1.kt +1 -5
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.kt +423 -117
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraNex.kt +4 -6
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Nimo.kt +2532 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/SGCManager.kt +17 -5
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Simulated.kt +3 -7
- package/android/src/main/java/com/mentra/bluetoothsdk/status/DeviceStatus.kt +0 -6
- package/android/src/main/java/com/mentra/bluetoothsdk/streaming/StreamModels.kt +16 -33
- package/android/src/main/java/com/mentra/bluetoothsdk/types/DeviceModels.kt +5 -3
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/Constants.kt +2 -1
- package/android/src/test/java/com/mentra/bluetoothsdk/camera/PhotoRequestTest.kt +60 -1
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/NimoProtocolTest.kt +333 -0
- package/build/BluetoothSdk.types.d.ts +50 -24
- package/build/BluetoothSdk.types.d.ts.map +1 -1
- package/build/BluetoothSdk.types.js +1 -0
- package/build/BluetoothSdk.types.js.map +1 -1
- package/build/_internal.d.ts +4 -4
- package/build/_internal.js +4 -4
- package/build/_internal.js.map +1 -1
- package/build/_private/BluetoothSdkModule.d.ts +14 -8
- package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
- package/build/_private/BluetoothSdkModule.js +4 -3
- package/build/_private/BluetoothSdkModule.js.map +1 -1
- package/build/_private/cameraRequestPayload.d.ts +4 -0
- package/build/_private/cameraRequestPayload.d.ts.map +1 -0
- package/build/_private/cameraRequestPayload.js +25 -0
- package/build/_private/cameraRequestPayload.js.map +1 -0
- package/build/_private/photoRequestPayload.d.ts.map +1 -1
- package/build/_private/photoRequestPayload.js +11 -3
- 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 +70 -61
- package/build/index.js.map +1 -1
- package/ios/BluetoothSdkModule.swift +51 -28
- package/ios/Source/Bridge.swift +4 -16
- package/ios/Source/DeviceManager.swift +76 -22
- package/ios/Source/DeviceStore.swift +0 -4
- package/ios/Source/MentraBluetoothSDK.swift +144 -78
- package/ios/Source/OtaManifest.swift +13 -13
- package/ios/Source/camera/CameraModels.swift +82 -20
- package/ios/Source/controllers/ControllerManager.swift +1 -2
- package/ios/Source/controllers/R1.swift +1 -2
- package/ios/Source/errors/{BluetoothError.swift → BluetoothSdkError.swift} +1 -1
- package/ios/Source/events/BluetoothEvents.swift +5 -27
- package/ios/Source/internal/BluetoothAvailability.swift +5 -5
- package/ios/Source/sgcs/Frame.swift +0 -2
- package/ios/Source/sgcs/G1.swift +52 -4
- package/ios/Source/sgcs/G2.swift +643 -204
- package/ios/Source/sgcs/Mach1.swift +1 -3
- package/ios/Source/sgcs/MentraLive.swift +64 -77
- package/ios/Source/sgcs/MentraNex.swift +1 -3
- package/ios/Source/sgcs/Nimo.swift +1928 -0
- package/ios/Source/sgcs/SGCManager.swift +16 -10
- package/ios/Source/sgcs/Simulated.swift +2 -6
- package/ios/Source/status/DeviceStatus.swift +0 -8
- package/ios/Source/streaming/StreamModels.swift +7 -32
- package/ios/Source/stt/STTTools.swift +1 -1
- package/ios/Source/types/DeviceModels.swift +5 -0
- package/ios/Source/utils/Constants.swift +2 -0
- package/package.json +6 -1
- package/src/BluetoothSdk.types.ts +54 -28
- package/src/_internal.ts +4 -4
- package/src/_private/BluetoothSdkModule.ts +20 -11
- package/src/_private/cameraRequestPayload.ts +29 -0
- package/src/_private/photoRequestPayload.ts +12 -3
- package/src/index.ts +77 -67
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
package com.mentra.bluetoothsdk
|
|
2
2
|
|
|
3
3
|
import org.json.JSONObject
|
|
4
|
+
import java.util.UUID
|
|
5
|
+
|
|
6
|
+
internal fun generatedCameraRequestId(prefix: String): String =
|
|
7
|
+
"$prefix-${System.currentTimeMillis()}-${UUID.randomUUID().toString().take(8)}"
|
|
8
|
+
|
|
9
|
+
internal fun nonBlankRequestId(requestId: String?): String? =
|
|
10
|
+
requestId?.trim()?.takeIf { it.isNotEmpty() }
|
|
4
11
|
|
|
5
12
|
enum class PhotoSize(val value: String) {
|
|
6
13
|
LOW("low"),
|
|
@@ -56,8 +63,8 @@ enum class PhotoCompression(val value: String) {
|
|
|
56
63
|
}
|
|
57
64
|
}
|
|
58
65
|
|
|
59
|
-
data class
|
|
60
|
-
val size:
|
|
66
|
+
data class PhotoCaptureDefaults(
|
|
67
|
+
val size: PhotoSize? = null,
|
|
61
68
|
val mfnr: Boolean? = null,
|
|
62
69
|
val zsl: Boolean? = null,
|
|
63
70
|
val noiseReduction: Boolean? = null,
|
|
@@ -71,7 +78,7 @@ data class ButtonPhotoSettings(
|
|
|
71
78
|
val resetCaptureTuning: Boolean = false,
|
|
72
79
|
)
|
|
73
80
|
|
|
74
|
-
data class
|
|
81
|
+
data class VideoRecordingDefaults(
|
|
75
82
|
val width: Int,
|
|
76
83
|
val height: Int,
|
|
77
84
|
val fps: Int,
|
|
@@ -138,13 +145,13 @@ data class CameraFovResult(
|
|
|
138
145
|
fallback: CameraFov,
|
|
139
146
|
): CameraFovResult {
|
|
140
147
|
if (ack.status == "error") {
|
|
141
|
-
throw
|
|
148
|
+
throw BluetoothSdkException(
|
|
142
149
|
ack.errorCode ?: "camera_fov_failed",
|
|
143
150
|
ack.errorMessage ?: "Camera FOV request failed.",
|
|
144
151
|
)
|
|
145
152
|
}
|
|
146
153
|
if (!ack.hardwareApplied) {
|
|
147
|
-
throw
|
|
154
|
+
throw BluetoothSdkException(
|
|
148
155
|
"camera_fov_not_applied",
|
|
149
156
|
"Camera FOV was saved but not applied to hardware.",
|
|
150
157
|
)
|
|
@@ -161,13 +168,11 @@ data class CameraFovResult(
|
|
|
161
168
|
}
|
|
162
169
|
|
|
163
170
|
data class PhotoRequest @JvmOverloads constructor(
|
|
164
|
-
val requestId: String,
|
|
165
|
-
val appId: String,
|
|
171
|
+
val requestId: String = generatedCameraRequestId("photo"),
|
|
166
172
|
val size: PhotoSize,
|
|
167
173
|
val webhookUrl: String,
|
|
168
174
|
val authToken: String? = null,
|
|
169
175
|
val compress: PhotoCompression = PhotoCompression.MEDIUM,
|
|
170
|
-
val flash: Boolean = true,
|
|
171
176
|
val save: Boolean = false,
|
|
172
177
|
val sound: Boolean = true,
|
|
173
178
|
/** Sensor exposure time for this capture only (ns), or null for auto exposure */
|
|
@@ -212,13 +217,12 @@ data class PhotoRequest @JvmOverloads constructor(
|
|
|
212
217
|
val ispAnalogGain = stringValue(values, "ispAnalogGain")
|
|
213
218
|
|
|
214
219
|
return PhotoRequest(
|
|
215
|
-
requestId = stringValue(values, "requestId", "request_id")
|
|
216
|
-
|
|
220
|
+
requestId = nonBlankRequestId(stringValue(values, "requestId", "request_id"))
|
|
221
|
+
?: generatedCameraRequestId("photo"),
|
|
217
222
|
size = PhotoSize.fromValue(stringValue(values, "size") ?: "medium"),
|
|
218
223
|
webhookUrl = stringValue(values, "webhookUrl", "webhook_url").orEmpty(),
|
|
219
224
|
authToken = stringValue(values, "authToken", "auth_token")?.takeIf { it.isNotBlank() },
|
|
220
225
|
compress = PhotoCompression.fromValue(stringValue(values, "compress") ?: "none"),
|
|
221
|
-
flash = boolValue(values, "flash") ?: true,
|
|
222
226
|
save = boolValue(values, "save", "saveToGallery") ?: false,
|
|
223
227
|
sound = boolValue(values, "sound") ?: true,
|
|
224
228
|
exposureTimeNs = exposureTimeNs,
|
|
@@ -441,3 +445,13 @@ data class PhotoStatusEvent(
|
|
|
441
445
|
val errorCode: String? get() = stringValue(values, "errorCode")
|
|
442
446
|
val errorMessage: String? get() = stringValue(values, "errorMessage")
|
|
443
447
|
}
|
|
448
|
+
|
|
449
|
+
data class CameraStatusEvent(
|
|
450
|
+
val values: Map<String, Any>,
|
|
451
|
+
) {
|
|
452
|
+
val requestId: String get() = stringValue(values, "requestId").orEmpty()
|
|
453
|
+
val state: String get() = stringValue(values, "state").orEmpty()
|
|
454
|
+
val timestamp: Long get() = longValue(values, "timestamp") ?: System.currentTimeMillis()
|
|
455
|
+
val errorCode: String? get() = stringValue(values, "errorCode")
|
|
456
|
+
val errorMessage: String? get() = stringValue(values, "errorMessage")
|
|
457
|
+
}
|
|
@@ -20,14 +20,13 @@ abstract class ControllerManager {
|
|
|
20
20
|
abstract fun startStream(message: Map<String, Any>)
|
|
21
21
|
abstract fun stopStream()
|
|
22
22
|
abstract fun sendStreamKeepAlive(message: Map<String, Any>)
|
|
23
|
-
abstract fun startVideoRecording(requestId: String, save: Boolean,
|
|
23
|
+
abstract fun startVideoRecording(requestId: String, save: Boolean, sound: Boolean)
|
|
24
24
|
abstract fun stopVideoRecording(requestId: String)
|
|
25
25
|
|
|
26
26
|
// Button Settings
|
|
27
27
|
abstract fun sendButtonPhotoSettings()
|
|
28
28
|
abstract fun sendButtonVideoRecordingSettings()
|
|
29
29
|
abstract fun sendButtonMaxRecordingTime()
|
|
30
|
-
abstract fun sendButtonCameraLedSetting()
|
|
31
30
|
|
|
32
31
|
// Display Control
|
|
33
32
|
abstract fun setBrightness(level: Int, autoMode: Boolean)
|
|
@@ -863,7 +863,7 @@ class R1 : ControllerManager() {
|
|
|
863
863
|
override fun sortMicRanking(list: MutableList<String>): MutableList<String> = list
|
|
864
864
|
override fun sendJson(jsonOriginal: Map<String, Any>, wakeUp: Boolean, requireAck: Boolean) {}
|
|
865
865
|
override fun requestPhoto(request: PhotoRequest) {}
|
|
866
|
-
override fun startVideoRecording(requestId: String, save: Boolean,
|
|
866
|
+
override fun startVideoRecording(requestId: String, save: Boolean, sound: Boolean) {}
|
|
867
867
|
override fun stopVideoRecording(requestId: String) {}
|
|
868
868
|
override fun startStream(message: Map<String, Any>) {}
|
|
869
869
|
override fun stopStream() {}
|
|
@@ -871,7 +871,6 @@ class R1 : ControllerManager() {
|
|
|
871
871
|
override fun sendButtonPhotoSettings() {}
|
|
872
872
|
override fun sendButtonVideoRecordingSettings() {}
|
|
873
873
|
override fun sendButtonMaxRecordingTime() {}
|
|
874
|
-
override fun sendButtonCameraLedSetting() {}
|
|
875
874
|
override fun setBrightness(level: Int, autoMode: Boolean) {}
|
|
876
875
|
override fun clearDisplay() {}
|
|
877
876
|
override fun sendTextWall(text: String) {}
|
|
@@ -39,27 +39,6 @@ data class SpeakingStatusEvent(
|
|
|
39
39
|
val values: Map<String, Any>,
|
|
40
40
|
)
|
|
41
41
|
|
|
42
|
-
data class OtaUpdateAvailableEvent(
|
|
43
|
-
val versionCode: Long?,
|
|
44
|
-
val versionName: String?,
|
|
45
|
-
val updates: List<String>,
|
|
46
|
-
val totalSize: Long?,
|
|
47
|
-
val cacheReady: Boolean?,
|
|
48
|
-
val values: Map<String, Any>,
|
|
49
|
-
) {
|
|
50
|
-
companion object {
|
|
51
|
-
internal fun fromMap(values: Map<String, Any>): OtaUpdateAvailableEvent =
|
|
52
|
-
OtaUpdateAvailableEvent(
|
|
53
|
-
versionCode = longValue(values, "version_code"),
|
|
54
|
-
versionName = stringValue(values, "version_name"),
|
|
55
|
-
updates = (values["updates"] as? List<*>)?.filterIsInstance<String>() ?: emptyList(),
|
|
56
|
-
totalSize = longValue(values, "total_size"),
|
|
57
|
-
cacheReady = boolValue(values, "cache_ready"),
|
|
58
|
-
values = values,
|
|
59
|
-
)
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
42
|
data class OtaStartAckEvent(
|
|
64
43
|
val timestamp: Long?,
|
|
65
44
|
val values: Map<String, Any>,
|
|
@@ -153,12 +132,12 @@ interface MentraBluetoothSdkListener {
|
|
|
153
132
|
fun onGalleryStatus(event: GalleryStatusEvent) {}
|
|
154
133
|
fun onPhotoResponse(event: PhotoResponseEvent) {}
|
|
155
134
|
fun onPhotoStatus(event: PhotoStatusEvent) {}
|
|
135
|
+
fun onCameraStatus(event: CameraStatusEvent) {}
|
|
156
136
|
fun onVideoRecordingStatus(event: VideoRecordingStatusEvent) {}
|
|
157
137
|
fun onMediaUpload(event: MediaUploadEvent) {}
|
|
158
138
|
fun onRgbLedControlResponse(event: RgbLedControlResponseEvent) {}
|
|
159
139
|
fun onStreamStatus(event: StreamStatusEvent) {}
|
|
160
140
|
fun onKeepAliveAck(event: KeepAliveAckEvent) {}
|
|
161
|
-
fun onOtaUpdateAvailable(event: OtaUpdateAvailableEvent) {}
|
|
162
141
|
fun onOtaStartAck(event: OtaStartAckEvent) {}
|
|
163
142
|
fun onOtaStatus(event: OtaStatusEvent) {}
|
|
164
143
|
fun onSettingsAck(event: SettingsAckEvent) {}
|
|
@@ -1584,7 +1584,7 @@ class G1 : SGCManager() {
|
|
|
1584
1584
|
|
|
1585
1585
|
}
|
|
1586
1586
|
|
|
1587
|
-
override fun startVideoRecording(requestId: String, save: Boolean,
|
|
1587
|
+
override fun startVideoRecording(requestId: String, save: Boolean, sound: Boolean) {
|
|
1588
1588
|
|
|
1589
1589
|
}
|
|
1590
1590
|
|
|
@@ -1600,10 +1600,6 @@ class G1 : SGCManager() {
|
|
|
1600
1600
|
|
|
1601
1601
|
}
|
|
1602
1602
|
|
|
1603
|
-
override fun sendButtonCameraLedSetting() {
|
|
1604
|
-
|
|
1605
|
-
}
|
|
1606
|
-
|
|
1607
1603
|
override fun sendCameraFovSetting() {
|
|
1608
1604
|
}
|
|
1609
1605
|
|
|
@@ -1618,25 +1614,72 @@ class G1 : SGCManager() {
|
|
|
1618
1614
|
|
|
1619
1615
|
override fun clearDisplay() {
|
|
1620
1616
|
Bridge.log("G1: clearDisplay() - sending space")
|
|
1621
|
-
|
|
1617
|
+
// Bypass the throttle (a clear must always land) and drop any pending caption so it can't
|
|
1618
|
+
// overwrite the clear after the fact.
|
|
1619
|
+
cancelPendingThrottledText()
|
|
1620
|
+
displayTextWall(" ")
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
// G1-specific display throttle (300ms, last-wins) — see G1.swift. G1 firmware can't absorb
|
|
1624
|
+
// rapid text-wall updates; coalesce to the latest within a 300ms window. Belongs in the G1 SGC
|
|
1625
|
+
// (G1 hardware quirk); G2 deliberately does NOT throttle (it must show every caption). The
|
|
1626
|
+
// trailing flush always sends the most recent text, so the final caption is never dropped —
|
|
1627
|
+
// only intermediate frames within a window are coalesced.
|
|
1628
|
+
private val textThrottleHandler = Handler(Looper.getMainLooper())
|
|
1629
|
+
private var textThrottlePending: String? = null
|
|
1630
|
+
private var textThrottleLastSent: Long = 0L
|
|
1631
|
+
private var textThrottleScheduled = false
|
|
1632
|
+
private val TEXT_THROTTLE_WINDOW_MS = 300L
|
|
1633
|
+
|
|
1634
|
+
/** Drop any pending throttled text-wall flush so it can't later overwrite a newer, non-text
|
|
1635
|
+
* display write (a clear, double-text-wall, or bitmap). The posted flush no-ops when
|
|
1636
|
+
* `textThrottlePending` is null. Called from every G1 display path that bypasses the throttle. */
|
|
1637
|
+
private fun cancelPendingThrottledText() {
|
|
1638
|
+
textThrottlePending = null
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
private fun throttledTextWall(text: String) {
|
|
1642
|
+
val now = android.os.SystemClock.uptimeMillis()
|
|
1643
|
+
val sinceLast = now - textThrottleLastSent
|
|
1644
|
+
if (sinceLast >= TEXT_THROTTLE_WINDOW_MS) {
|
|
1645
|
+
// Past the window — send now.
|
|
1646
|
+
textThrottleLastSent = now
|
|
1647
|
+
textThrottlePending = null
|
|
1648
|
+
displayTextWall(text)
|
|
1649
|
+
} else {
|
|
1650
|
+
// Inside the window — keep only the latest and schedule one trailing flush.
|
|
1651
|
+
textThrottlePending = text
|
|
1652
|
+
if (!textThrottleScheduled) {
|
|
1653
|
+
textThrottleScheduled = true
|
|
1654
|
+
textThrottleHandler.postDelayed({
|
|
1655
|
+
textThrottleScheduled = false
|
|
1656
|
+
val pending = textThrottlePending ?: return@postDelayed
|
|
1657
|
+
textThrottlePending = null
|
|
1658
|
+
textThrottleLastSent = android.os.SystemClock.uptimeMillis()
|
|
1659
|
+
displayTextWall(pending)
|
|
1660
|
+
}, TEXT_THROTTLE_WINDOW_MS - sinceLast)
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1622
1663
|
}
|
|
1623
1664
|
|
|
1624
1665
|
override fun sendText(text: String) {
|
|
1625
1666
|
Bridge.log("G1: sendText() - text: " + text)
|
|
1626
|
-
|
|
1667
|
+
throttledTextWall(text)
|
|
1627
1668
|
}
|
|
1628
1669
|
|
|
1629
1670
|
override fun sendTextWall(text: String) {
|
|
1630
1671
|
// Bridge.log("G1: sendTextWall() - text: " + text);
|
|
1631
|
-
|
|
1672
|
+
throttledTextWall(text)
|
|
1632
1673
|
}
|
|
1633
1674
|
|
|
1634
1675
|
override fun sendDoubleTextWall(top: String, bottom: String) {
|
|
1676
|
+
cancelPendingThrottledText() // a newer layout supersedes any pending caption text
|
|
1635
1677
|
Bridge.log("G1: sendDoubleTextWall() - top: " + top + ", bottom: " + bottom)
|
|
1636
1678
|
displayDoubleTextWall(top, bottom)
|
|
1637
1679
|
}
|
|
1638
1680
|
|
|
1639
1681
|
override fun displayBitmap(base64ImageData: String, x: Int?, y: Int?, width: Int?, height: Int?): Boolean {
|
|
1682
|
+
cancelPendingThrottledText() // a bitmap supersedes any pending caption text
|
|
1640
1683
|
try {
|
|
1641
1684
|
// Decode base64 to byte array
|
|
1642
1685
|
val bmpData = android.util.Base64.decode(base64ImageData, android.util.Base64.DEFAULT)
|