@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
@@ -24,6 +24,8 @@ class MessageChunker {
24
24
  private static let MIN_CHUNK_DATA_SIZE = 4
25
25
  private static let MAX_PACKED_CHUNK_SIZE = 253
26
26
  private static let K900_FRAME_OVERHEAD = 7
27
+ private static let MAX_BINARY_FRAGMENT_PAYLOAD = BleWireProtocol.maxFragmentPayload
28
+ private static let MAX_BINARY_FRAME_SIZE = BleWireProtocol.mtuTarget
27
29
 
28
30
  /**
29
31
  * Check if a message needs to be chunked
@@ -204,6 +206,77 @@ class MessageChunker {
204
206
  )
205
207
  }
206
208
 
209
+ static func needsBinaryFragmenting(_ payload: Data) -> Bool {
210
+ payload.count > MAX_BINARY_FRAGMENT_PAYLOAD
211
+ }
212
+
213
+ static func needsBinaryFragmenting(_ json: String) -> Bool {
214
+ guard let data = json.data(using: .utf8) else { return false }
215
+ return needsBinaryFragmenting(data)
216
+ }
217
+
218
+ struct BinaryFragment {
219
+ let flags: UInt8
220
+ let msgId: UInt16
221
+ let fragIdx: UInt8
222
+ let fragCount: UInt8
223
+ let payload: Data
224
+ }
225
+
226
+ static func createBinaryFragments(
227
+ payload: Data,
228
+ msgId: UInt16 = 0,
229
+ wakeUp: Bool = false,
230
+ ackRequested: Bool = false
231
+ ) -> [BinaryFragment] {
232
+ let fragCount = max(1, (payload.count + MAX_BINARY_FRAGMENT_PAYLOAD - 1) / MAX_BINARY_FRAGMENT_PAYLOAD)
233
+ guard fragCount <= 255 else { return [] }
234
+
235
+ var fragments: [BinaryFragment] = []
236
+ for i in 0 ..< fragCount {
237
+ let offset = i * MAX_BINARY_FRAGMENT_PAYLOAD
238
+ let end = min(offset + MAX_BINARY_FRAGMENT_PAYLOAD, payload.count)
239
+ let fragPayload = payload.subdata(in: offset ..< end)
240
+
241
+ var flags: UInt8 = 0
242
+ if i == 0 { flags |= BleWireProtocol.flagFirstFrag }
243
+ if i == fragCount - 1 { flags |= BleWireProtocol.flagLastFrag }
244
+ if wakeUp, i == 0 { flags |= BleWireProtocol.flagWake }
245
+ if ackRequested, i == fragCount - 1 { flags |= BleWireProtocol.flagAckRequested }
246
+
247
+ fragments.append(
248
+ BinaryFragment(
249
+ flags: flags,
250
+ msgId: msgId,
251
+ fragIdx: UInt8(i),
252
+ fragCount: UInt8(fragCount),
253
+ payload: fragPayload
254
+ )
255
+ )
256
+ }
257
+
258
+ print("MessageChunker: Created \(fragments.count) binary fragments for \(payload.count) bytes")
259
+ return fragments
260
+ }
261
+
262
+ static func allBinaryFragmentsFit(_ fragments: [BinaryFragment]) -> Bool {
263
+ for fragment in fragments {
264
+ guard let packed = BleWireProtocol.packBinaryFragment(
265
+ flags: fragment.flags,
266
+ msgId: fragment.msgId,
267
+ fragIdx: fragment.fragIdx,
268
+ fragCount: fragment.fragCount,
269
+ payload: fragment.payload
270
+ ) else {
271
+ return false
272
+ }
273
+ if packed.count > MAX_BINARY_FRAME_SIZE {
274
+ return false
275
+ }
276
+ }
277
+ return true
278
+ }
279
+
207
280
  /**
208
281
  * Container for chunk information
209
282
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mentra/bluetooth-sdk",
3
- "version": "0.1.18",
3
+ "version": "0.1.20",
4
4
  "description": "SDK for communicating with smart glasses",
5
5
  "main": "build/index.js",
6
6
  "react-native": "src/index.ts",
@@ -8,28 +8,33 @@
8
8
  "types": "build/index.d.ts",
9
9
  "exports": {
10
10
  ".": {
11
- "types": "./build/index.d.ts",
12
11
  "react-native": "./src/index.ts",
12
+ "types": "./build/index.d.ts",
13
13
  "default": "./build/index.js"
14
14
  },
15
15
  "./internal": {
16
- "types": "./build/_internal.d.ts",
17
16
  "react-native": "./src/_internal.ts",
17
+ "types": "./build/_internal.d.ts",
18
18
  "default": "./build/_internal.js"
19
19
  },
20
20
  "./react": {
21
- "types": "./build/react/index.d.ts",
22
21
  "react-native": "./src/react/index.ts",
22
+ "types": "./build/react/index.d.ts",
23
23
  "default": "./build/react/index.js"
24
24
  },
25
+ "./types": {
26
+ "react-native": "./src/types/index.ts",
27
+ "types": "./build/types/index.d.ts",
28
+ "default": "./build/types/index.js"
29
+ },
25
30
  "./photo-receiver": {
26
- "types": "./build/photo-receiver/index.d.ts",
27
31
  "react-native": "./src/photo-receiver/index.ts",
32
+ "types": "./build/photo-receiver/index.d.ts",
28
33
  "default": "./build/photo-receiver/index.js"
29
34
  },
30
35
  "./debug": {
31
- "types": "./build/debug.d.ts",
32
36
  "react-native": "./src/debug.ts",
37
+ "types": "./build/debug.d.ts",
33
38
  "default": "./build/debug.js"
34
39
  },
35
40
  "./_private/*": null,
@@ -102,7 +107,9 @@
102
107
  },
103
108
  "dependencies": {},
104
109
  "devDependencies": {
105
- "expo-module-scripts": "^55.0.2"
110
+ "@types/node": "^25.9.3",
111
+ "expo-module-scripts": "^55.0.2",
112
+ "typescript": "~5.9.2"
106
113
  },
107
114
  "peerDependencies": {
108
115
  "@expo/config-plugins": ">=8.0.0",
@@ -440,8 +440,14 @@ export type SettingsAckSuccessEvent = Omit<SettingsAckEvent, "status"> & {
440
440
  export type RgbLedAction = "on" | "off"
441
441
  export type RgbLedColor = "red" | "green" | "blue" | "orange" | "white"
442
442
  export type PhotoSize = "low" | "medium" | "high" | "max"
443
+ export type PhotoMode = "photo" | "text"
443
444
  export type ButtonPhotoSize = "low" | "medium" | "high" | "max"
444
445
 
446
+ /**
447
+ * @deprecated Sticky action-button photo presets via {@link BluetoothSdkPublicModule.setPhotoCaptureDefaults}
448
+ * are deprecated. Prefer per-request {@link BluetoothSdkPublicModule.requestPhoto} options
449
+ * (e.g. `mode: "text"` for AE ÷3) instead of persisting button-photo tuning on the glasses.
450
+ */
445
451
  export type PhotoCaptureDefaults = {
446
452
  size?: PhotoSize
447
453
  mfnr?: boolean
@@ -542,7 +548,9 @@ export type MicMode = "phone" | "glasses" | "bluetoothClassic" | "bluetooth"
542
548
 
543
549
  export type PhotoRequestParams = {
544
550
  requestId?: string
551
+ appId?: string
545
552
  size: PhotoSize
553
+ mode?: PhotoMode
546
554
  webhookUrl: string | null
547
555
  authToken: string | null
548
556
  compress: PhotoCompression
@@ -689,11 +697,25 @@ export type StreamResolvedConfig = {
689
697
  }
690
698
  }
691
699
 
700
+ /** Live encoder telemetry, when the glasses emit it (no firmware does yet). */
701
+ export type StreamLiveStats = {
702
+ /** Current encoded video bitrate in bits per second. */
703
+ bitrate?: number
704
+ /** Current encode frame rate. */
705
+ fps?: number
706
+ droppedFrames?: number
707
+ /** Seconds since the stream started. */
708
+ duration?: number
709
+ /** Device temperature in °C, if the hardware reports it. */
710
+ temperatureC?: number
711
+ }
712
+
692
713
  type StreamStatusCommon = {
693
714
  type: "stream_status"
694
715
  streamId?: string
695
716
  timestamp?: number
696
717
  resolvedConfig?: StreamResolvedConfig
718
+ stats?: StreamLiveStats
697
719
  }
698
720
 
699
721
  export type StreamStatusEvent =
@@ -761,6 +783,15 @@ export type OtaStartAckEvent = {
761
783
  timestamp: number
762
784
  }
763
785
 
786
+ export type OtaUpdateAvailableEvent = {
787
+ type: "ota_update_available"
788
+ version_code?: number
789
+ version_name?: string
790
+ updates?: string[]
791
+ total_size?: number
792
+ cache_ready?: boolean
793
+ }
794
+
764
795
  export type OtaStatusEvent = {
765
796
  type: "ota_status"
766
797
  session_id: string
@@ -837,6 +868,7 @@ export type BluetoothSdkModuleEvents = {
837
868
  stream_status: (event: StreamStatusEvent) => void
838
869
  keep_alive_ack: (event: KeepAliveAckEvent) => void
839
870
  mtk_update_complete: (event: MtkUpdateCompleteEvent) => void
871
+ ota_update_available: (event: OtaUpdateAvailableEvent) => void
840
872
  ota_start_ack: (event: OtaStartAckEvent) => void
841
873
  ota_status: (event: OtaStatusEvent) => void
842
874
  version_info: (event: VersionInfoEvent) => void
@@ -907,6 +939,7 @@ export type BluetoothSdkEventMap = {
907
939
  mic_pcm: MicPcmEvent
908
940
  mic_lc3: MicLc3Event
909
941
  stream_status: StreamStatusEvent
942
+ ota_update_available: OtaUpdateAvailableEvent
910
943
  ota_start_ack: OtaStartAckEvent
911
944
  ota_status: OtaStatusEvent
912
945
  version_info: VersionInfoEvent
@@ -960,6 +993,11 @@ export interface BluetoothSdkPublicModule {
960
993
 
961
994
  setGalleryModeEnabled(enabled: boolean): Promise<SettingsAckSuccessEvent>
962
995
  setVoiceActivityDetectionEnabled(enabled: boolean): Promise<void>
996
+ /**
997
+ * @deprecated Sticky action-button photo presets are deprecated. Prefer per-request
998
+ * `requestPhoto(...)` options (e.g. `mode: "text"` for AE ÷3, or explicit per-shot
999
+ * fields). Still functional until removed in a future release.
1000
+ */
963
1001
  setPhotoCaptureDefaults(settings: PhotoCaptureDefaults): Promise<SettingsAckSuccessEvent>
964
1002
  setVideoRecordingDefaults(settings: VideoRecordingDefaults): Promise<SettingsAckSuccessEvent>
965
1003
  setMaxVideoRecordingDuration(minutes: number): Promise<SettingsAckSuccessEvent>
@@ -1085,6 +1123,7 @@ export interface OtaUpdateInfo {
1085
1123
  versionName: string
1086
1124
  updates: string[] // ["apk", "mtk", "bes"]
1087
1125
  totalSize: number
1126
+ cacheReady?: boolean
1088
1127
  }
1089
1128
 
1090
1129
  export interface OtaProgress {
@@ -1270,7 +1309,6 @@ export type BluetoothSettingsUpdate = Partial<{
1270
1309
  should_send_lc3: boolean
1271
1310
  should_send_transcript: boolean
1272
1311
  offline_mode: boolean
1273
- offline_captions_running: boolean
1274
1312
  local_stt_fallback_active: boolean
1275
1313
  pending_wearable: DeviceModel | ""
1276
1314
  default_wearable: DeviceModel | ""
@@ -124,6 +124,10 @@ declare class BluetoothSdkNativeModule extends NativeModule<BluetoothSdkModuleEv
124
124
  // Gallery Commands
125
125
  setGalleryModeEnabled(enabled: boolean): Promise<SettingsAckSuccessEvent>
126
126
  setVoiceActivityDetectionEnabled(enabled: boolean): Promise<void>
127
+ /**
128
+ * @deprecated Sticky action-button photo presets are deprecated. Prefer per-request
129
+ * `requestPhoto(...)` options (e.g. `mode: "text"` for AE ÷3).
130
+ */
127
131
  setPhotoCaptureDefaults(settings: PhotoCaptureDefaults): Promise<SettingsAckSuccessEvent>
128
132
  setVideoRecordingDefaults(width: number, height: number, fps: number): Promise<SettingsAckSuccessEvent>
129
133
  setMaxVideoRecordingDuration(minutes: number): Promise<SettingsAckSuccessEvent>
@@ -30,6 +30,7 @@ export function photoRequestParamsForNative(
30
30
  ): Record<string, string | number | boolean> {
31
31
  const payload: Record<string, string | number | boolean> = {
32
32
  size: normalizePhotoSizeTier(params.size),
33
+ mode: params.mode ?? "photo",
33
34
  webhookUrl: params.webhookUrl ?? "",
34
35
  compress: params.compress,
35
36
  sound: params.sound,
package/src/index.ts CHANGED
@@ -96,6 +96,11 @@ export const BluetoothSdk: BluetoothSdkPublicModule = Object.freeze({
96
96
  setHotspotState: bindPublicMethod("setHotspotState"),
97
97
  setGalleryModeEnabled: bindPublicMethod("setGalleryModeEnabled"),
98
98
  setVoiceActivityDetectionEnabled: bindPublicMethod("setVoiceActivityDetectionEnabled"),
99
+ /**
100
+ * @deprecated Sticky action-button photo presets are deprecated. Prefer per-request
101
+ * `requestPhoto(...)` options (e.g. `mode: "text"` for AE ÷3, or explicit per-shot
102
+ * fields). Still functional until removed in a future release.
103
+ */
99
104
  setPhotoCaptureDefaults: bindPublicMethod("setPhotoCaptureDefaults"),
100
105
  setVideoRecordingDefaults: ({width, height, fps}: VideoRecordingDefaults) => {
101
106
  const method = (PrivateBluetoothSdkModule as unknown as Record<string, unknown>).setVideoRecordingDefaults
@@ -204,6 +209,7 @@ export type {
204
209
  PhotoCompression,
205
210
  PhotoFpsRange,
206
211
  PhotoMeteredPreview,
212
+ PhotoMode,
207
213
  PhotoResponseEvent,
208
214
  PhotoRequestedCaptureConfig,
209
215
  PhotoRequestParams,
@@ -0,0 +1,7 @@
1
+ export {
2
+ isBusyGlassesConnectionStatus,
3
+ isConnectedGlassesConnectionStatus,
4
+ isReadyGlassesConnectionStatus,
5
+ } from "../BluetoothSdk.types"
6
+
7
+ export type {ConnectedGlassesConnectionStatus, GlassesConnectionStatus} from "../BluetoothSdk.types"