@mentra/bluetooth-sdk 0.1.19 → 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 +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +5 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +12 -0
- 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/MentraLive.kt +113 -147
- package/android/src/main/java/com/mentra/bluetoothsdk/types/DeviceModels.kt +2 -3
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/BlePhotoUploadService.java +65 -8
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/K900ProtocolUtils.java +6 -5
- package/android/src/test/java/com/mentra/bluetoothsdk/BluetoothSdkExceptionTest.kt +18 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/camera/PhotoRequestTest.kt +16 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/G1TextSanitizerTest.kt +31 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/utils/BlePhotoUploadServiceTest.java +9 -0
- package/build/BluetoothSdk.types.d.ts +25 -0
- 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/ios/Source/BluetoothSdkDefaults.swift +1 -1
- package/ios/Source/DeviceManager.swift +2 -1
- package/ios/Source/DeviceStore.swift +5 -0
- package/ios/Source/MentraBluetoothSDK.swift +14 -1
- package/ios/Source/camera/CameraModels.swift +17 -3
- package/ios/Source/sgcs/G1.swift +1 -1
- package/ios/Source/sgcs/MentraLive.swift +140 -17
- package/ios/Source/sgcs/MentraLiveL2capChannel.swift +183 -0
- package/ios/Source/utils/G1Text.swift +30 -1
- package/package.json +1 -1
- package/src/BluetoothSdk.types.ts +26 -0
- package/src/_private/BluetoothSdkModule.ts +4 -0
- package/src/_private/photoRequestPayload.ts +1 -0
- package/src/index.ts +6 -0
package/README.md
CHANGED
|
@@ -475,7 +475,7 @@ For bare native iOS apps, use the public SwiftPM repository:
|
|
|
475
475
|
https://github.com/Mentra-Community/mentra-bluetooth-sdk-ios.git
|
|
476
476
|
```
|
|
477
477
|
|
|
478
|
-
Select version `0.1.
|
|
478
|
+
Select version `0.1.20`, then add the `MentraBluetoothSDK` product to your app target.
|
|
479
479
|
|
|
480
480
|
For local SDK development, add this package folder directly in Xcode:
|
|
481
481
|
|
|
@@ -1,13 +1,92 @@
|
|
|
1
|
+
@file:Suppress("FunctionName")
|
|
2
|
+
|
|
1
3
|
package com.mentra.bluetoothsdk
|
|
2
4
|
|
|
3
5
|
import com.mentra.bluetoothsdk.debug.BleTraceLogger
|
|
4
6
|
import com.mentra.bluetoothsdk.utils.DeviceTypes
|
|
7
|
+
import expo.modules.kotlin.exception.CodedException
|
|
5
8
|
import expo.modules.kotlin.functions.Coroutine
|
|
6
9
|
import expo.modules.kotlin.modules.Module
|
|
7
10
|
import expo.modules.kotlin.modules.ModuleDefinition
|
|
11
|
+
import expo.modules.kotlin.modules.ModuleDefinitionBuilder
|
|
8
12
|
import kotlinx.coroutines.Dispatchers
|
|
9
13
|
import kotlinx.coroutines.withContext
|
|
10
14
|
|
|
15
|
+
// Expo has no module-level error mapper, so SDK-backed registrations translate
|
|
16
|
+
// core exceptions here while keeping Expo types out of the native SDK API.
|
|
17
|
+
private inline fun <T> withExpoSdkError(block: () -> T): T =
|
|
18
|
+
try {
|
|
19
|
+
block()
|
|
20
|
+
} catch (error: BluetoothSdkException) {
|
|
21
|
+
throw CodedException(error.code, error.message ?: error.code, error)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
private suspend inline fun <T> withExpoSdkErrorSuspend(crossinline block: suspend () -> T): T =
|
|
25
|
+
try {
|
|
26
|
+
block()
|
|
27
|
+
} catch (error: BluetoothSdkException) {
|
|
28
|
+
throw CodedException(error.code, error.message ?: error.code, error)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
private inline fun <reified R> ModuleDefinitionBuilder.SdkAsyncFunction(
|
|
32
|
+
name: String,
|
|
33
|
+
crossinline body: () -> R,
|
|
34
|
+
) = AsyncFunction<R>(name) { withExpoSdkError { body() } }
|
|
35
|
+
|
|
36
|
+
private inline fun <reified R, reified P0> ModuleDefinitionBuilder.SdkAsyncFunction(
|
|
37
|
+
name: String,
|
|
38
|
+
crossinline body: (P0) -> R,
|
|
39
|
+
) = AsyncFunction<R, P0>(name) { p0 -> withExpoSdkError { body(p0) } }
|
|
40
|
+
|
|
41
|
+
private inline fun <reified R, reified P0, reified P1> ModuleDefinitionBuilder.SdkAsyncFunction(
|
|
42
|
+
name: String,
|
|
43
|
+
crossinline body: (P0, P1) -> R,
|
|
44
|
+
) = AsyncFunction<R, P0, P1>(name) { p0, p1 -> withExpoSdkError { body(p0, p1) } }
|
|
45
|
+
|
|
46
|
+
private inline fun <reified R, reified P0, reified P1, reified P2> ModuleDefinitionBuilder.SdkAsyncFunction(
|
|
47
|
+
name: String,
|
|
48
|
+
crossinline body: (P0, P1, P2) -> R,
|
|
49
|
+
) = AsyncFunction<R, P0, P1, P2>(name) { p0, p1, p2 -> withExpoSdkError { body(p0, p1, p2) } }
|
|
50
|
+
|
|
51
|
+
private inline fun <reified R, reified P0, reified P1, reified P2, reified P3>
|
|
52
|
+
ModuleDefinitionBuilder.SdkAsyncFunction(
|
|
53
|
+
name: String,
|
|
54
|
+
crossinline body: (P0, P1, P2, P3) -> R,
|
|
55
|
+
) = AsyncFunction<R, P0, P1, P2, P3>(name) { p0, p1, p2, p3 ->
|
|
56
|
+
withExpoSdkError { body(p0, p1, p2, p3) }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private inline fun <
|
|
60
|
+
reified R,
|
|
61
|
+
reified P0,
|
|
62
|
+
reified P1,
|
|
63
|
+
reified P2,
|
|
64
|
+
reified P3,
|
|
65
|
+
reified P4,
|
|
66
|
+
reified P5,
|
|
67
|
+
reified P6,
|
|
68
|
+
> ModuleDefinitionBuilder.SdkAsyncFunction(
|
|
69
|
+
name: String,
|
|
70
|
+
crossinline body: (P0, P1, P2, P3, P4, P5, P6) -> R,
|
|
71
|
+
) = AsyncFunction<R, P0, P1, P2, P3, P4, P5, P6>(name) { p0, p1, p2, p3, p4, p5, p6 ->
|
|
72
|
+
withExpoSdkError { body(p0, p1, p2, p3, p4, p5, p6) }
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
private inline fun <reified R> ModuleDefinitionBuilder.SdkFunction(
|
|
76
|
+
name: String,
|
|
77
|
+
crossinline body: () -> R,
|
|
78
|
+
) = Function<R>(name) { withExpoSdkError { body() } }
|
|
79
|
+
|
|
80
|
+
private inline fun <reified R, reified P0> ModuleDefinitionBuilder.SdkFunction(
|
|
81
|
+
name: String,
|
|
82
|
+
crossinline body: (P0) -> R,
|
|
83
|
+
) = Function<R, P0>(name) { p0 -> withExpoSdkError { body(p0) } }
|
|
84
|
+
|
|
85
|
+
private inline fun <reified R> ModuleDefinitionBuilder.SdkCoroutineFunction(
|
|
86
|
+
name: String,
|
|
87
|
+
crossinline body: suspend () -> R,
|
|
88
|
+
) = AsyncFunction(name) Coroutine { -> withExpoSdkErrorSuspend { body() } }
|
|
89
|
+
|
|
11
90
|
class BluetoothSdkModule : Module() {
|
|
12
91
|
private var sdk: MentraBluetoothSdk? = null
|
|
13
92
|
private var deviceManager: DeviceManager? = null
|
|
@@ -183,9 +262,10 @@ class BluetoothSdkModule : Module() {
|
|
|
183
262
|
|
|
184
263
|
private fun requireSdk(): MentraBluetoothSdk =
|
|
185
264
|
sdk
|
|
186
|
-
?: throw
|
|
265
|
+
?: throw CodedException(
|
|
187
266
|
"sdk_not_initialized",
|
|
188
267
|
"Bluetooth SDK is not initialized.",
|
|
268
|
+
null,
|
|
189
269
|
)
|
|
190
270
|
|
|
191
271
|
override fun definition() = ModuleDefinition {
|
|
@@ -335,11 +415,11 @@ class BluetoothSdkModule : Module() {
|
|
|
335
415
|
|
|
336
416
|
// MARK: - Display Commands
|
|
337
417
|
|
|
338
|
-
|
|
418
|
+
SdkAsyncFunction("displayEvent") { params: Map<String, Any> ->
|
|
339
419
|
sdk?.displayEvent(DisplayEventRequest(params))
|
|
340
420
|
}
|
|
341
421
|
|
|
342
|
-
|
|
422
|
+
SdkAsyncFunction("displayText") { text: String, x: Int?, y: Int?, size: Int? ->
|
|
343
423
|
sdk?.displayText(
|
|
344
424
|
text = text,
|
|
345
425
|
x = x ?: 0,
|
|
@@ -348,34 +428,35 @@ class BluetoothSdkModule : Module() {
|
|
|
348
428
|
)
|
|
349
429
|
}
|
|
350
430
|
|
|
351
|
-
|
|
431
|
+
SdkAsyncFunction("clearDisplay") { -> sdk?.clearDisplay() }
|
|
352
432
|
|
|
353
433
|
// MARK: - Connection Commands
|
|
354
434
|
|
|
355
|
-
|
|
435
|
+
SdkAsyncFunction("connectDefault") { -> sdk?.connectDefault() }
|
|
356
436
|
|
|
357
|
-
|
|
437
|
+
SdkAsyncFunction("connectDefaultWithOptions") { options: Map<String, Any> ->
|
|
358
438
|
sdk?.connectDefault(options.toMentraConnectOptions())
|
|
359
439
|
}
|
|
360
440
|
|
|
361
|
-
|
|
441
|
+
SdkAsyncFunction("setDefaultDevice") { device: Map<String, Any>? ->
|
|
362
442
|
sdk?.setDefaultDevice(device.toMentraDevice())
|
|
363
443
|
}
|
|
364
444
|
|
|
365
|
-
|
|
445
|
+
SdkAsyncFunction("clearDefaultDevice") { -> sdk?.clearDefaultDevice() }
|
|
366
446
|
|
|
367
|
-
|
|
447
|
+
SdkAsyncFunction("connectWithOptions") { device: Map<String, Any>, options: Map<String, Any> ->
|
|
368
448
|
sdk?.connect(
|
|
369
|
-
device.toMentraDevice()
|
|
449
|
+
device.toMentraDevice()
|
|
450
|
+
?: throw IllegalArgumentException("connect requires a Device with model and name."),
|
|
370
451
|
options.toMentraConnectOptions(),
|
|
371
452
|
)
|
|
372
453
|
}
|
|
373
454
|
|
|
374
|
-
|
|
455
|
+
SdkAsyncFunction("connectSimulated") { -> sdk?.connectSimulated() }
|
|
375
456
|
|
|
376
|
-
|
|
457
|
+
SdkAsyncFunction("disconnect") { -> sdk?.disconnect() }
|
|
377
458
|
|
|
378
|
-
|
|
459
|
+
SdkAsyncFunction("forget") { -> sdk?.forget() }
|
|
379
460
|
|
|
380
461
|
AsyncFunction("connectDefaultController") { deviceManager?.connectDefaultController() }
|
|
381
462
|
|
|
@@ -383,15 +464,15 @@ class BluetoothSdkModule : Module() {
|
|
|
383
464
|
|
|
384
465
|
AsyncFunction("forgetController") { deviceManager?.forgetController() }
|
|
385
466
|
|
|
386
|
-
|
|
467
|
+
SdkAsyncFunction("startScan") { model: String ->
|
|
387
468
|
sdk?.startScan(DeviceModel.fromDeviceType(model))
|
|
388
469
|
}
|
|
389
470
|
|
|
390
|
-
|
|
471
|
+
SdkAsyncFunction("stopScan") { -> sdk?.stopScan() }
|
|
391
472
|
|
|
392
|
-
|
|
473
|
+
SdkAsyncFunction("cancelConnectionAttempt") { -> sdk?.cancelConnectionAttempt() }
|
|
393
474
|
|
|
394
|
-
|
|
475
|
+
SdkAsyncFunction("showDashboard") { -> sdk?.showDashboard() }
|
|
395
476
|
|
|
396
477
|
AsyncFunction("ping") { deviceManager?.ping() }
|
|
397
478
|
|
|
@@ -409,51 +490,54 @@ class BluetoothSdkModule : Module() {
|
|
|
409
490
|
|
|
410
491
|
// MARK: - Incident Reporting
|
|
411
492
|
|
|
412
|
-
|
|
493
|
+
SdkAsyncFunction("sendIncidentId") { incidentId: String, apiBaseUrl: String? ->
|
|
413
494
|
sdk?.sendIncidentId(incidentId, apiBaseUrl)
|
|
414
495
|
}
|
|
415
496
|
|
|
416
497
|
// MARK: - WiFi Commands
|
|
417
498
|
|
|
418
|
-
|
|
499
|
+
SdkAsyncFunction("requestWifiScan") { -> requireSdk().requestWifiScan().map { it.toMap() } }
|
|
419
500
|
|
|
420
|
-
|
|
501
|
+
SdkAsyncFunction("sendWifiCredentials") { ssid: String, password: String ->
|
|
421
502
|
requireSdk().sendWifiCredentials(ssid, password).values
|
|
422
503
|
}
|
|
423
504
|
|
|
424
|
-
|
|
505
|
+
SdkAsyncFunction("forgetWifiNetwork") { ssid: String ->
|
|
506
|
+
requireSdk().forgetWifiNetwork(ssid).values
|
|
507
|
+
}
|
|
425
508
|
|
|
426
|
-
|
|
509
|
+
SdkAsyncFunction("setHotspotState") { enabled: Boolean ->
|
|
427
510
|
requireSdk().setHotspotState(enabled).values
|
|
428
511
|
}
|
|
429
512
|
|
|
430
|
-
|
|
513
|
+
SdkAsyncFunction("setSystemTime") { timestampMs: Double ->
|
|
431
514
|
sdk?.setSystemTime(timestampMs.toLong())
|
|
432
515
|
}
|
|
433
516
|
|
|
434
517
|
// MARK: - Gallery Commands
|
|
435
518
|
|
|
436
|
-
|
|
519
|
+
SdkAsyncFunction("setGalleryModeEnabled") { enabled: Boolean ->
|
|
437
520
|
requireSdk().setGalleryModeEnabled(enabled).values
|
|
438
521
|
}
|
|
439
522
|
|
|
440
|
-
|
|
523
|
+
SdkAsyncFunction("setVoiceActivityDetectionEnabled") { enabled: Boolean ->
|
|
441
524
|
sdk?.setVoiceActivityDetectionEnabled(enabled)
|
|
442
525
|
}
|
|
443
526
|
|
|
444
|
-
|
|
527
|
+
@Suppress("DEPRECATION")
|
|
528
|
+
SdkAsyncFunction("setPhotoCaptureDefaults") { params: Map<String, Any?> ->
|
|
445
529
|
requireSdk().setPhotoCaptureDefaults(params.toPhotoCaptureDefaults()).values
|
|
446
530
|
}
|
|
447
531
|
|
|
448
|
-
|
|
532
|
+
SdkAsyncFunction("setVideoRecordingDefaults") { width: Int, height: Int, fps: Int ->
|
|
449
533
|
requireSdk().setVideoRecordingDefaults(VideoRecordingDefaults(width, height, fps)).values
|
|
450
534
|
}
|
|
451
535
|
|
|
452
|
-
|
|
536
|
+
SdkAsyncFunction("setMaxVideoRecordingDuration") { minutes: Int ->
|
|
453
537
|
requireSdk().setMaxVideoRecordingDuration(minutes).values
|
|
454
538
|
}
|
|
455
539
|
|
|
456
|
-
|
|
540
|
+
SdkAsyncFunction("setCameraFov") { fov: Map<String, Any> ->
|
|
457
541
|
val value = (fov["fov"] as? Number)?.toInt() ?: CameraFov.DEFAULT_FOV
|
|
458
542
|
val roiPosition = CameraRoiPosition.fromValue(
|
|
459
543
|
(fov["roiPosition"] as? Number)?.toInt()
|
|
@@ -462,13 +546,13 @@ class BluetoothSdkModule : Module() {
|
|
|
462
546
|
requireSdk().setCameraFov(CameraFov(value, roiPosition)).values
|
|
463
547
|
}
|
|
464
548
|
|
|
465
|
-
|
|
549
|
+
SdkAsyncFunction("setCameraTuningConfig") { anrOn: Boolean, gainOn: Boolean ->
|
|
466
550
|
requireSdk().setCameraTuningConfig(anrOn, gainOn).values
|
|
467
551
|
}
|
|
468
552
|
|
|
469
|
-
|
|
553
|
+
SdkAsyncFunction("queryGalleryStatus") { -> requireSdk().queryGalleryStatus().values }
|
|
470
554
|
|
|
471
|
-
|
|
555
|
+
SdkAsyncFunction("requestPhoto") { params: Map<String, Any?> ->
|
|
472
556
|
// JS may pass null for optional fields; Map<String, Any> rejects null values at the bridge.
|
|
473
557
|
val sanitized =
|
|
474
558
|
params.mapNotNull { (key, value) ->
|
|
@@ -476,12 +560,12 @@ class BluetoothSdkModule : Module() {
|
|
|
476
560
|
}.toMap()
|
|
477
561
|
val req = PhotoRequest.fromMap(sanitized)
|
|
478
562
|
Bridge.log(
|
|
479
|
-
"NATIVE: PHOTO PIPELINE [3/6] BluetoothSdk.requestPhoto requestId=${req.requestId} size=${req.size} compress=${req.compress} sound=${req.sound} exposureTimeNs=${req.exposureTimeNs} iso=${req.iso}"
|
|
563
|
+
"NATIVE: PHOTO PIPELINE [3/6] BluetoothSdk.requestPhoto requestId=${req.requestId} size=${req.size} mode=${req.mode.value} compress=${req.compress} sound=${req.sound} exposureTimeNs=${req.exposureTimeNs} iso=${req.iso}"
|
|
480
564
|
)
|
|
481
565
|
requireSdk().requestPhoto(req).values
|
|
482
566
|
}
|
|
483
567
|
|
|
484
|
-
|
|
568
|
+
SdkAsyncFunction("warmUpCamera") { params: Map<String, Any?> ->
|
|
485
569
|
val requestId = params["requestId"] as? String
|
|
486
570
|
val size = PhotoSize.fromValue(params["size"] as? String)
|
|
487
571
|
val exposureRaw = (params["exposureTimeNs"] as? Number)?.toDouble()
|
|
@@ -494,19 +578,19 @@ class BluetoothSdkModule : Module() {
|
|
|
494
578
|
|
|
495
579
|
// MARK: - OTA Commands
|
|
496
580
|
|
|
497
|
-
|
|
581
|
+
SdkFunction("setOtaVersionUrl") { otaVersionUrl: String ->
|
|
498
582
|
requireSdk().setOtaVersionUrl(otaVersionUrl)
|
|
499
583
|
}
|
|
500
584
|
|
|
501
|
-
|
|
585
|
+
SdkFunction("getOtaVersionUrl") { -> requireSdk().getOtaVersionUrl() }
|
|
502
586
|
|
|
503
587
|
// Runs on Dispatchers.IO, not the shared Expo AsyncFunctionQueue:
|
|
504
588
|
// manifest fetches and version waits can block for several seconds.
|
|
505
|
-
|
|
589
|
+
SdkCoroutineFunction("checkForOtaUpdate") {
|
|
506
590
|
withContext(Dispatchers.IO) { requireSdk().checkForOtaUpdate() }
|
|
507
591
|
}
|
|
508
592
|
|
|
509
|
-
|
|
593
|
+
SdkAsyncFunction("startOtaUpdate") { otaVersionUrl: String? ->
|
|
510
594
|
val sdk = requireSdk()
|
|
511
595
|
if (otaVersionUrl.isNullOrBlank()) {
|
|
512
596
|
sdk.startOtaUpdate().values
|
|
@@ -515,21 +599,21 @@ class BluetoothSdkModule : Module() {
|
|
|
515
599
|
}
|
|
516
600
|
}
|
|
517
601
|
|
|
518
|
-
|
|
602
|
+
SdkAsyncFunction("sendOtaQueryStatus") { -> requireSdk().sendOtaQueryStatus().values }
|
|
519
603
|
|
|
520
604
|
// MARK: - Version Info Commands
|
|
521
605
|
|
|
522
|
-
|
|
606
|
+
SdkAsyncFunction("requestVersionInfo") { -> requireSdk().requestVersionInfo().toMap() }
|
|
523
607
|
|
|
524
608
|
// MARK: - Power Control Commands
|
|
525
609
|
|
|
526
|
-
|
|
610
|
+
SdkAsyncFunction("sendShutdown") { -> sdk?.sendShutdown() }
|
|
527
611
|
|
|
528
|
-
|
|
612
|
+
SdkAsyncFunction("sendReboot") { -> sdk?.sendReboot() }
|
|
529
613
|
|
|
530
614
|
// MARK: - Video Recording Commands
|
|
531
615
|
|
|
532
|
-
|
|
616
|
+
SdkAsyncFunction("startVideoRecording") {
|
|
533
617
|
requestId: String,
|
|
534
618
|
save: Boolean,
|
|
535
619
|
sound: Boolean,
|
|
@@ -553,7 +637,7 @@ class BluetoothSdkModule : Module() {
|
|
|
553
637
|
|
|
554
638
|
// webhookUrl/authToken are supplied at stop (not start) so the token is
|
|
555
639
|
// fresh when the upload runs. Empty/null webhook = keep on device.
|
|
556
|
-
|
|
640
|
+
SdkAsyncFunction("stopVideoRecording") {
|
|
557
641
|
requestId: String,
|
|
558
642
|
webhookUrl: String?,
|
|
559
643
|
authToken: String? ->
|
|
@@ -562,23 +646,23 @@ class BluetoothSdkModule : Module() {
|
|
|
562
646
|
|
|
563
647
|
// MARK: - Stream Commands
|
|
564
648
|
|
|
565
|
-
|
|
649
|
+
SdkAsyncFunction("startStream") { params: Map<String, Any> ->
|
|
566
650
|
requireSdk().startStream(StreamRequest.fromMap(params)).values
|
|
567
651
|
}
|
|
568
652
|
|
|
569
|
-
|
|
653
|
+
SdkAsyncFunction("startExternallyManagedStream") { params: Map<String, Any> ->
|
|
570
654
|
requireSdk().startExternallyManagedStream(StreamRequest.fromMap(params)).values
|
|
571
655
|
}
|
|
572
656
|
|
|
573
|
-
|
|
657
|
+
SdkAsyncFunction("stopStream") { -> requireSdk().stopStream().values }
|
|
574
658
|
|
|
575
|
-
|
|
659
|
+
SdkAsyncFunction("sendExternallyManagedStreamKeepAlive") { params: Map<String, Any> ->
|
|
576
660
|
sdk?.sendExternallyManagedStreamKeepAlive(StreamKeepAliveRequest.fromMap(params))
|
|
577
661
|
}
|
|
578
662
|
|
|
579
663
|
// MARK: - Microphone Commands
|
|
580
664
|
|
|
581
|
-
|
|
665
|
+
SdkAsyncFunction("setMicState") {
|
|
582
666
|
enabled: Boolean,
|
|
583
667
|
useGlassesMic: Boolean?,
|
|
584
668
|
sendTranscript: Boolean?,
|
|
@@ -600,7 +684,7 @@ class BluetoothSdkModule : Module() {
|
|
|
600
684
|
|
|
601
685
|
// MARK: - Audio Playback Monitoring
|
|
602
686
|
|
|
603
|
-
|
|
687
|
+
SdkAsyncFunction("setOwnAppAudioPlaying") { playing: Boolean ->
|
|
604
688
|
sdk?.setOwnAppAudioPlaying(playing)
|
|
605
689
|
}
|
|
606
690
|
|
|
@@ -619,7 +703,7 @@ class BluetoothSdkModule : Module() {
|
|
|
619
703
|
|
|
620
704
|
// MARK: - RGB LED Control
|
|
621
705
|
|
|
622
|
-
|
|
706
|
+
SdkAsyncFunction("rgbLedControl") {
|
|
623
707
|
requestId: String,
|
|
624
708
|
packageName: String?,
|
|
625
709
|
action: String,
|
|
@@ -1800,7 +1800,7 @@ class DeviceManager {
|
|
|
1800
1800
|
iso = manualIso,
|
|
1801
1801
|
)
|
|
1802
1802
|
Bridge.log(
|
|
1803
|
-
"MAN: PHOTO PIPELINE [4/6] DeviceManager.requestPhoto requestId=${routed.requestId} size=${routed.size.value} compress=${routed.compress.value} save=${routed.save} sound=${routed.sound} exposureTimeNs=$exposureNs iso=${manualIso ?: "auto"} aeDivisor=${routed.aeExposureDivisor} isoCap=${routed.isoCap} sgc=${sgc?.javaClass?.simpleName ?: "null"}"
|
|
1803
|
+
"MAN: PHOTO PIPELINE [4/6] DeviceManager.requestPhoto requestId=${routed.requestId} size=${routed.size.value} mode=${routed.mode.value} compress=${routed.compress.value} save=${routed.save} sound=${routed.sound} exposureTimeNs=$exposureNs iso=${manualIso ?: "auto"} aeDivisor=${routed.aeExposureDivisor} isoCap=${routed.isoCap} sgc=${sgc?.javaClass?.simpleName ?: "null"}"
|
|
1804
1804
|
)
|
|
1805
1805
|
val activeSgc = sgc
|
|
1806
1806
|
if (activeSgc == null) {
|
|
@@ -292,6 +292,11 @@ object DeviceStore {
|
|
|
292
292
|
"bluetooth" to "camera_fov" -> {
|
|
293
293
|
DeviceManager.getInstance().sgc?.sendCameraFovSetting()
|
|
294
294
|
}
|
|
295
|
+
"bluetooth" to "button_video_settings" -> {
|
|
296
|
+
DeviceManager.getInstance().sgc?.sendButtonVideoRecordingSettings()
|
|
297
|
+
}
|
|
298
|
+
// Legacy scalar keys remain supported for older hosts. New code should write the
|
|
299
|
+
// canonical button_video_settings object so width/height/fps update atomically.
|
|
295
300
|
"bluetooth" to "button_video_width",
|
|
296
301
|
"bluetooth" to "button_video_height",
|
|
297
302
|
"bluetooth" to "button_video_fps" -> {
|
|
@@ -477,6 +477,12 @@ class MentraBluetoothSdk private constructor(
|
|
|
477
477
|
DeviceStore.apply(ObservableStore.BLUETOOTH_CATEGORY, "voice_activity_detection_enabled", enabled)
|
|
478
478
|
}
|
|
479
479
|
|
|
480
|
+
@Deprecated(
|
|
481
|
+
message =
|
|
482
|
+
"Sticky action-button photo presets are deprecated. Prefer per-request " +
|
|
483
|
+
"requestPhoto(...) options (e.g. mode=TEXT for AE ÷3, or explicit per-shot fields). " +
|
|
484
|
+
"This method still works but will be removed in a future release.",
|
|
485
|
+
)
|
|
480
486
|
fun setPhotoCaptureDefaults(settings: PhotoCaptureDefaults): SettingsAckEvent =
|
|
481
487
|
performSettingsCommand(
|
|
482
488
|
setting = "button_photo",
|
|
@@ -534,6 +540,12 @@ class MentraBluetoothSdk private constructor(
|
|
|
534
540
|
performSettingsCommand(
|
|
535
541
|
setting = "button_video_recording",
|
|
536
542
|
updateStore = { _ ->
|
|
543
|
+
DeviceStore.set(
|
|
544
|
+
ObservableStore.BLUETOOTH_CATEGORY,
|
|
545
|
+
"button_video_settings",
|
|
546
|
+
mapOf("width" to defaults.width, "height" to defaults.height, "fps" to defaults.fps),
|
|
547
|
+
)
|
|
548
|
+
// Keep legacy cache keys readable for older internal callers during migration.
|
|
537
549
|
DeviceStore.set(ObservableStore.BLUETOOTH_CATEGORY, "button_video_width", defaults.width)
|
|
538
550
|
DeviceStore.set(ObservableStore.BLUETOOTH_CATEGORY, "button_video_height", defaults.height)
|
|
539
551
|
DeviceStore.set(ObservableStore.BLUETOOTH_CATEGORY, "button_video_fps", defaults.fps)
|
|
@@ -167,6 +167,17 @@ data class CameraFovResult(
|
|
|
167
167
|
}
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
+
enum class PhotoMode(val value: String) {
|
|
171
|
+
PHOTO("photo"),
|
|
172
|
+
TEXT("text");
|
|
173
|
+
|
|
174
|
+
companion object {
|
|
175
|
+
@JvmStatic
|
|
176
|
+
fun fromValue(value: String?): PhotoMode =
|
|
177
|
+
values().firstOrNull { it.value == value } ?: PHOTO
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
170
181
|
data class PhotoRequest @JvmOverloads constructor(
|
|
171
182
|
val requestId: String = generatedCameraRequestId("photo"),
|
|
172
183
|
val size: PhotoSize,
|
|
@@ -188,6 +199,7 @@ data class PhotoRequest @JvmOverloads constructor(
|
|
|
188
199
|
val ispDigitalGain: Int? = null,
|
|
189
200
|
val ispAnalogGain: String? = null,
|
|
190
201
|
val resetCaptureTuning: Boolean? = null,
|
|
202
|
+
val mode: PhotoMode = PhotoMode.PHOTO,
|
|
191
203
|
) {
|
|
192
204
|
companion object {
|
|
193
205
|
/** Mirrors iOS `BluetoothSdkModule` defaults for keys omitted from the JS bridge. */
|
|
@@ -225,6 +237,7 @@ data class PhotoRequest @JvmOverloads constructor(
|
|
|
225
237
|
compress = PhotoCompression.fromValue(stringValue(values, "compress") ?: "none"),
|
|
226
238
|
save = boolValue(values, "save", "saveToGallery") ?: false,
|
|
227
239
|
sound = boolValue(values, "sound") ?: true,
|
|
240
|
+
mode = PhotoMode.fromValue(stringValue(values, "mode")),
|
|
228
241
|
exposureTimeNs = exposureTimeNs,
|
|
229
242
|
iso = iso,
|
|
230
243
|
aeExposureDivisor = aeDivisor,
|
|
@@ -2841,7 +2841,7 @@ class G1 : SGCManager() {
|
|
|
2841
2841
|
private fun chunkTextForTransmission(text: String?): List<ByteArray> {
|
|
2842
2842
|
// Handle empty or whitespace-only text by sending at least a space
|
|
2843
2843
|
// This ensures the display gets updated/cleared properly
|
|
2844
|
-
val textToSend = if (text == null || text.trim().isEmpty()) " " else text
|
|
2844
|
+
val textToSend = if (text == null || text.trim().isEmpty()) " " else sanitizeG1DisplayText(text)
|
|
2845
2845
|
val textBytes = textToSend.toByteArray(StandardCharsets.UTF_8)
|
|
2846
2846
|
val totalChunks = Math.ceil(textBytes.size.toDouble() / MAX_CHUNK_SIZE).toInt()
|
|
2847
2847
|
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
package com.mentra.bluetoothsdk.sgcs
|
|
2
|
+
|
|
3
|
+
import java.text.Normalizer
|
|
4
|
+
|
|
5
|
+
private val G1_COMBINING_MARKS = Regex("\\p{M}+")
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Converts text to the base Latin glyphs available in the Even Realities G1 firmware.
|
|
9
|
+
*
|
|
10
|
+
* G1 does not ship glyphs for combining diacritics. Normalize at the device boundary so
|
|
11
|
+
* miniapps can retain the real text everywhere else and newer glasses receive it unchanged.
|
|
12
|
+
*/
|
|
13
|
+
internal fun sanitizeG1DisplayText(text: String): String {
|
|
14
|
+
val expanded = buildString(text.length) {
|
|
15
|
+
text.forEach { character ->
|
|
16
|
+
when (character) {
|
|
17
|
+
'Đ', 'Ð' -> append('D')
|
|
18
|
+
'đ', 'ð' -> append('d')
|
|
19
|
+
'Ł' -> append('L')
|
|
20
|
+
'ł' -> append('l')
|
|
21
|
+
'Ø' -> append('O')
|
|
22
|
+
'ø' -> append('o')
|
|
23
|
+
'Æ' -> append("AE")
|
|
24
|
+
'æ' -> append("ae")
|
|
25
|
+
'Œ' -> append("OE")
|
|
26
|
+
'œ' -> append("oe")
|
|
27
|
+
'ẞ' -> append("SS")
|
|
28
|
+
'ß' -> append("ss")
|
|
29
|
+
'Þ' -> append("TH")
|
|
30
|
+
'þ' -> append("th")
|
|
31
|
+
else -> append(character)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return Normalizer.normalize(expanded, Normalizer.Form.NFD)
|
|
37
|
+
.replace(G1_COMBINING_MARKS, "")
|
|
38
|
+
}
|