@glassly/bluetooth-sdk 0.1.0-dev.99 → 0.2.8-dev.444

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 (155) hide show
  1. package/README.md +47 -6
  2. package/android/build.gradle +7 -7
  3. package/android/lc3Lib/build.gradle +2 -2
  4. package/android/src/main/assets/G2Firmware/README.md +27 -2
  5. package/android/src/main/assets/G2Firmware/{g2-stock-2.2.9.22.evenota.bin → g2-glassly-cfw-2.3.0.24.evenota.bin} +0 -0
  6. package/android/src/main/assets/G2Firmware/{g2-glassly-cfw-2.2.9.22.evenota.bin → g2-stock-2.3.0.24.evenota.bin} +0 -0
  7. package/android/src/main/java/com/glassly/bluetoothsdk/BluetoothSdk.kt +38 -0
  8. package/android/src/main/java/com/glassly/bluetoothsdk/BluetoothSdkModule.kt +50 -14
  9. package/android/src/main/java/com/glassly/bluetoothsdk/Bridge.kt +18 -0
  10. package/android/src/main/java/com/glassly/bluetoothsdk/DeviceManager.kt +139 -10
  11. package/android/src/main/java/com/glassly/bluetoothsdk/DeviceStore.kt +28 -1
  12. package/android/src/main/java/com/glassly/bluetoothsdk/DisplayPacer.kt +199 -0
  13. package/android/src/main/java/com/glassly/bluetoothsdk/GeneratedChangelogCatalog.kt +10 -1
  14. package/android/src/main/java/com/glassly/bluetoothsdk/GeneratedReleaseMetadata.kt +4 -4
  15. package/android/src/main/java/com/glassly/bluetoothsdk/GlassesNotifications.kt +155 -0
  16. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2.kt +1065 -255
  17. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2BleLink.kt +119 -0
  18. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2CfwClassification.kt +29 -28
  19. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2CfwRenderer.kt +138 -33
  20. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2CfwScene.kt +673 -0
  21. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2CfwTextureCache.kt +235 -162
  22. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2CfwTransport.kt +402 -0
  23. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2DisplayDiagnostics.kt +84 -0
  24. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2FlashProtocol.kt +3 -3
  25. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2Flasher.kt +9 -9
  26. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2Keyboard.kt +689 -0
  27. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2KeyboardBridge.kt +452 -0
  28. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2KeyboardHid.kt +208 -0
  29. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2NotificationFile.kt +181 -0
  30. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2RingBattery.kt +20 -0
  31. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2Sensors.kt +67 -0
  32. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2TouchInput.kt +39 -0
  33. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/MentraLive.kt +5 -5
  34. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/MentraNex.kt +14 -14
  35. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/{GlasslyBle.java → NexProto.java} +6462 -7618
  36. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/NotificationMessageIds.kt +58 -0
  37. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/SGCManager.kt +77 -2
  38. package/android/src/main/java/com/glassly/bluetoothsdk/speakerid/SpeakerIdEngine.kt +1 -1
  39. package/android/src/main/java/com/glassly/bluetoothsdk/status/DeviceStatus.kt +55 -0
  40. package/android/src/main/java/com/glassly/bluetoothsdk/stt/STTTools.kt +1 -1
  41. package/android/src/main/java/com/glassly/bluetoothsdk/stt/SherpaOnnxTranscriber.kt +1 -1
  42. package/android/src/main/java/com/glassly/bluetoothsdk/tts/TTSTools.kt +1 -1
  43. package/android/src/main/java/com/glassly/bluetoothsdk/utils/NexSGCUtils.kt +24 -149
  44. package/android/src/test/java/com/glassly/bluetoothsdk/DisplayPacerTest.kt +277 -0
  45. package/android/src/test/java/com/glassly/bluetoothsdk/G2BleLinkTest.kt +70 -0
  46. package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2CfwClassificationTest.kt +45 -56
  47. package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2CfwRendererTest.kt +78 -5
  48. package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2CfwSceneTest.kt +489 -0
  49. package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2CfwTextureCacheTest.kt +100 -43
  50. package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2CfwTransportTest.kt +252 -0
  51. package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2DisplayDiagnosticsTest.kt +64 -0
  52. package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2KeyboardTest.kt +271 -0
  53. package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2NotificationFileTest.kt +204 -0
  54. package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2RingBatteryTest.kt +56 -0
  55. package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2SensorsTest.kt +56 -0
  56. package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2TouchInputTest.kt +60 -0
  57. package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/NexProtoDescriptorTest.kt +32 -0
  58. package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/NotificationMessageIdsTest.kt +58 -0
  59. package/build/BluetoothSdk.types.d.ts +209 -20
  60. package/build/BluetoothSdk.types.d.ts.map +1 -1
  61. package/build/BluetoothSdk.types.js.map +1 -1
  62. package/build/_internal.d.ts +1 -1
  63. package/build/_internal.d.ts.map +1 -1
  64. package/build/_internal.js.map +1 -1
  65. package/build/_private/BluetoothSdkModule.d.ts +30 -1
  66. package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
  67. package/build/_private/BluetoothSdkModule.js.map +1 -1
  68. package/build/generated/changelogCatalog.d.ts +28 -1
  69. package/build/generated/changelogCatalog.d.ts.map +1 -1
  70. package/build/generated/changelogCatalog.js +37 -1
  71. package/build/generated/changelogCatalog.js.map +1 -1
  72. package/build/generated/releaseMetadata.js +4 -4
  73. package/build/generated/releaseMetadata.js.map +1 -1
  74. package/build/index.d.ts +1 -1
  75. package/build/index.d.ts.map +1 -1
  76. package/build/index.js +14 -0
  77. package/build/index.js.map +1 -1
  78. package/ios/BluetoothSdkModule.swift +102 -13
  79. package/ios/GlasslyBluetoothSDK.podspec +1 -1
  80. package/ios/Packages/SherpaOnnx/SherpaOnnx.swift +8 -3
  81. package/ios/Source/BluetoothSDK.swift +53 -0
  82. package/ios/Source/BluetoothSdkDefaults.swift +1 -1
  83. package/ios/Source/Bridge.swift +13 -0
  84. package/ios/Source/DeviceManager.swift +151 -33
  85. package/ios/Source/DeviceStore.swift +28 -14
  86. package/ios/Source/DisplayPacer.swift +180 -0
  87. package/ios/Source/GeneratedChangelogCatalog.swift +10 -1
  88. package/ios/Source/GeneratedReleaseMetadata.swift +4 -4
  89. package/ios/Source/GlassesNotifications.swift +181 -0
  90. package/ios/Source/ObservableStore.swift +14 -2
  91. package/ios/Source/Resources/G2Firmware/README.md +27 -2
  92. package/ios/Source/Resources/G2Firmware/{g2-stock-2.2.9.22.evenota.bin → g2-glassly-cfw-2.3.0.24.evenota.bin} +0 -0
  93. package/ios/Source/Resources/G2Firmware/{g2-glassly-cfw-2.2.9.22.evenota.bin → g2-stock-2.3.0.24.evenota.bin} +0 -0
  94. package/ios/Source/controllers/R1.swift +0 -1
  95. package/ios/Source/internal/BluetoothAvailability.swift +31 -0
  96. package/ios/Source/sgcs/G2.swift +1420 -495
  97. package/ios/Source/sgcs/G2AncsRelay.swift +268 -0
  98. package/ios/Source/sgcs/G2BleLink.swift +102 -0
  99. package/ios/Source/sgcs/G2CfwClassification.swift +45 -27
  100. package/ios/Source/sgcs/G2CfwRenderer.swift +166 -33
  101. package/ios/Source/sgcs/G2CfwScene.swift +779 -0
  102. package/ios/Source/sgcs/G2CfwTextureCache.swift +244 -117
  103. package/ios/Source/sgcs/G2CfwTransport.swift +485 -0
  104. package/ios/Source/sgcs/G2DisplayDiagnostics.swift +154 -0
  105. package/ios/Source/sgcs/G2FlashProtocol.swift +2 -2
  106. package/ios/Source/sgcs/G2Flasher.swift +9 -9
  107. package/ios/Source/sgcs/G2Keyboard.swift +780 -0
  108. package/ios/Source/sgcs/G2KeyboardBridge.swift +438 -0
  109. package/ios/Source/sgcs/G2KeyboardHid.swift +290 -0
  110. package/ios/Source/sgcs/G2NotificationTuning.swift +24 -0
  111. package/ios/Source/sgcs/G2RingBattery.swift +22 -0
  112. package/ios/Source/sgcs/G2Sensors.swift +75 -0
  113. package/ios/Source/sgcs/G2TouchInput.swift +51 -0
  114. package/ios/Source/sgcs/G2WritePacing.swift +18 -0
  115. package/ios/Source/sgcs/MentraNex.swift +44 -44
  116. package/ios/Source/sgcs/SGCManager.swift +120 -13
  117. package/ios/Source/sgcs/{glassly_ble.pb.swift → nex_ble.pb.swift} +405 -465
  118. package/ios/Source/speakerid/SpeakerIdEngine.swift +2 -2
  119. package/ios/Source/status/DeviceStatus.swift +27 -0
  120. package/ios/Source/stt/SherpaOnnxTranscriber.swift +15 -14
  121. package/ios/Source/utils/Enums.swift +0 -1
  122. package/ios/Source/utils/G1Text.swift +0 -1
  123. package/ios/Source/utils/G2NotifyFile.swift +20 -10
  124. package/ios/Source/utils/JSCExperiment.swift +6 -6
  125. package/ios/Source/utils/Models.swift +0 -1
  126. package/ios/Tests/BluetoothSDKTests/BluetoothAvailabilityTests.swift +29 -0
  127. package/ios/Tests/BluetoothSDKTests/DisplayPacerTests.swift +255 -0
  128. package/ios/Tests/BluetoothSDKTests/G2AncsRelayTests.swift +186 -0
  129. package/ios/Tests/BluetoothSDKTests/G2BleLinkTests.swift +52 -0
  130. package/ios/Tests/BluetoothSDKTests/G2CfwClassificationTests.swift +46 -51
  131. package/ios/Tests/BluetoothSDKTests/G2CfwRendererTests.swift +164 -5
  132. package/ios/Tests/BluetoothSDKTests/G2CfwSceneTests.swift +497 -0
  133. package/ios/Tests/BluetoothSDKTests/G2CfwTextureCacheTests.swift +108 -44
  134. package/ios/Tests/BluetoothSDKTests/G2CfwTransportTests.swift +259 -0
  135. package/ios/Tests/BluetoothSDKTests/G2DisplayDiagnosticsTests.swift +89 -0
  136. package/ios/Tests/BluetoothSDKTests/G2FlashProtocolTests.swift +3 -2
  137. package/ios/Tests/BluetoothSDKTests/G2ImageAckTests.swift +104 -0
  138. package/ios/Tests/BluetoothSDKTests/G2KeyboardTests.swift +274 -0
  139. package/ios/Tests/BluetoothSDKTests/G2NotifyFileTests.swift +21 -5
  140. package/ios/Tests/BluetoothSDKTests/G2RingBatteryTests.swift +56 -0
  141. package/ios/Tests/BluetoothSDKTests/G2SensorsTests.swift +56 -0
  142. package/ios/Tests/BluetoothSDKTests/G2TouchInputTests.swift +71 -0
  143. package/ios/Tests/BluetoothSDKTests/G2WritePacingTests.swift +35 -0
  144. package/ios/Tests/BluetoothSDKTests/GlassesNotificationTests.swift +95 -0
  145. package/package.json +1 -1
  146. package/plugin/build/withAndroid.js +2 -2
  147. package/plugin/build/withIos.js +1 -1
  148. package/scripts/gen-nex-proto.sh +12 -0
  149. package/scripts/inject-ios-sdk-version.mjs +1 -1
  150. package/src/BluetoothSdk.types.ts +214 -19
  151. package/src/_internal.ts +1 -1
  152. package/src/_private/BluetoothSdkModule.ts +40 -0
  153. package/src/generated/changelogCatalog.ts +37 -1
  154. package/src/generated/releaseMetadata.ts +4 -4
  155. package/src/index.ts +24 -0
package/README.md CHANGED
@@ -7,7 +7,7 @@ The package includes:
7
7
  - A React Native / Expo module API exposed as `BluetoothSdk`.
8
8
  - React hooks under `@glassly/bluetooth-sdk/react` for common scan,
9
9
  connection, status, and event lifecycles.
10
- - Native Android code published as `com.glassly:bluetooth-sdk`.
10
+ - Native Android code published as `ai.glassly:bluetooth-sdk`.
11
11
  - Native iOS code available as the `BluetoothSDK` Swift package.
12
12
  - An Expo config plugin that wires the native dependencies into generated Android and iOS projects.
13
13
 
@@ -352,7 +352,7 @@ Android and iOS async APIs use `BluetoothSdkException` / `BluetoothSdkError` for
352
352
  React Native apps should use `MentraLiveOtaFlow` or `useMentraLiveOta` from
353
353
  `@glassly/engine/ota`. They provide the same tested Wi-Fi/hotspot,
354
354
  APK/MTK/BES, restart, retry, and verification flow as the Glassly App. See
355
- [Update Mentra Live](https://docs.glassly.com/bluetooth-sdk/software-update).
355
+ [Update Mentra Live](https://docs.glassly.ai/bluetooth-sdk/software-update).
356
356
 
357
357
  The Bluetooth SDK exposes the lower-level commands and transport capabilities
358
358
  that Glassly Engine and native apps build on. They are not a replacement for the
@@ -409,7 +409,7 @@ OTA requires Mentra Live glasses firmware that supports the ASG OTA protocol and
409
409
 
410
410
  For release selection, bundle verification, analytics opt-out, internal server
411
411
  requirements, and disconnected validation, see the
412
- [air-gapped deployment guide](https://docs.glassly.com/bluetooth-sdk/air-gapped-deployment).
412
+ [air-gapped deployment guide](https://docs.glassly.ai/bluetooth-sdk/air-gapped-deployment).
413
413
 
414
414
  Mentra Live also rejects `ota_start` before acknowledgement when its known battery level is below 5%, emitting a failed `ota_status` with `error_message: "battery_low"`. Unknown battery state remains fail-open, so apps should keep their own (typically stricter) user-facing battery policy.
415
415
 
@@ -458,6 +458,47 @@ await BluetoothSdk.stopStream()
458
458
  Use `rtmp://` or `rtmps://` for RTMP, `srt://` for SRT, and `http://` or `https://` for WHIP/WebRTC ingest. `startStream()` resolves with the correlated `stream_status` event once the glasses report `status: "streaming"`; `stopStream()` resolves when the glasses report `status: "stopped"` or confirms the stream was already stopped / not streaming. `stopStream()` returns a normalized stopped event for that already-stopped case. Stream starts reject if the glasses report an error before streaming; stream stops reject for real stop errors, send failure, another stop in flight, or timeout. Stream video input fields are `width`, `height`, `bitrate`, and `fps`. The SDK sends stream keep-alives automatically while streaming and reports keep-alive failures through `stream_status`. The camera light is always enabled while streaming.
459
459
  `stream_status` events may include `resolvedConfig`, which reports the effective transport, video, and audio settings after glasses defaults, clamps, and camera preflight. The resolved effective frame rate is reported as `resolvedConfig.video.fps`. While live, supported firmware emits periodic `streaming` events whose `stats` object contains `bitrate` (bits per second), `fps`, `droppedFrames`, `duration` (seconds), and `temperatureC` when the CPU sensor is available.
460
460
 
461
+ ## Glasses Notifications
462
+
463
+ Some glasses present phone notifications themselves (popup + history) and take
464
+ a glasses-neutral config. The SDK describes what the wearer wants; each driver
465
+ maps it onto its own protocol and snaps values its firmware cannot take.
466
+
467
+ ```ts
468
+ await BluetoothSdk.setGlassesNotificationConfig({
469
+ enabled: true,
470
+ popups: true, // false keeps history without popups
471
+ popupSeconds: 5, // 0 lets the firmware choose; drivers snap other values
472
+ holdDuringApps: true, // hold popups while navigation or a miniapp is on screen
473
+ blockedApps: [], // package names (Android) or bundle ids (iOS)
474
+ allowedApps: [], // known ids minus blocked, for firmware whose filter is an allowlist
475
+ })
476
+ const status = await BluetoothSdk.getGlassesNotificationStatus()
477
+ const subscription = BluetoothSdk.addListener('glasses_notification_status', handleStatus)
478
+ ```
479
+
480
+ `setGlassesNotificationConfig` rejects when no glasses are connected or the
481
+ connected driver has no notification surface. The status reports `supported`,
482
+ the `feed` (`ancs` when the glasses read the phone's notifications themselves,
483
+ `phone_upload` when the phone hands them content, `none`), the wearer's
484
+ `access` consent where one applies, and a `phase`. `applied` means the config
485
+ was written to the link, not acknowledged by firmware; `stale` means a
486
+ reconnect is needed. Drivers re-apply their last config after every reconnect.
487
+
488
+ The G2 driver takes the config on both platforms. On iOS the glasses read the
489
+ phone's notifications directly and the config also drives the on-glass
490
+ allowlist file. On Android the phone uploads each notification as a small
491
+ file over the glasses' file-transfer services; uploads are queued (eight
492
+ pending, oldest dropped), serialized, and reported by id through
493
+ `notification_upload_result` (`sent`, `failed`, `cancelled`, `dropped`) with no
494
+ content attached. Disabling or disconnecting cancels queued uploads.
495
+
496
+ `notification_upload_result` is typed and emitted, but it is not yet in the
497
+ public listener allowlist, so `addListener("notification_upload_result", ...)`
498
+ throws today. Until that is fixed, treat per-notification outcomes as
499
+ diagnostic-only and drive UI from `getGlassesNotificationStatus()` and the
500
+ `glasses_notification_status` event.
501
+
461
502
  ## Events
462
503
 
463
504
  React Native components should use `useBluetoothEvent()` for hardware events:
@@ -509,7 +550,7 @@ For normal app development, install the JavaScript package from npm. For SDK sou
509
550
 
510
551
  ```sh
511
552
  bun add --no-save /path/to/Glassly/mobile/modules/bluetooth-sdk
512
- GLASSLY_BLUETOOTH_SDK_PACKAGE_PATH=/path/to/Glassly/mobile/modules/bluetooth-sdk bunx expo run:ios
553
+ BLUETOOTH_SDK_PACKAGE_PATH=/path/to/Glassly/mobile/modules/bluetooth-sdk bunx expo run:ios
513
554
  ```
514
555
 
515
556
  Use `bunx expo run:android` for Android. Keep local paths in your shell or CI environment, not in committed app config.
@@ -523,7 +564,7 @@ Glassly repo root:
523
564
 
524
565
  The `android/` folder in this package is source for the generated Expo Android
525
566
  project, not the local Gradle entrypoint. The check script prepares
526
- `mobile/android` and uses its Gradle wrapper with `-PglasslyPublicSdk=true`,
567
+ `mobile/android` and uses its Gradle wrapper with `-PpublicSdk=true`,
527
568
  matching the CI release workflow's public SDK dependency mode.
528
569
 
529
570
  For bare native iOS apps, use the public SwiftPM repository:
@@ -551,7 +592,7 @@ Maintainers publishing the native Android artifacts to Maven Central should
551
592
  follow [RELEASING_ANDROID_MAVEN.md](./RELEASING_ANDROID_MAVEN.md).
552
593
 
553
594
  Public Maven publishing uses a public SDK mode that omits Glassly-only Android
554
- integrations from the artifact metadata while normal Glassly app builds keep
595
+ integrations from the artifact metadata while normal Glassly App builds keep
555
596
  those integrations enabled.
556
597
 
557
598
  Use `android/gradle.properties.example` as the template for Sonatype Central and
@@ -7,8 +7,8 @@ apply plugin: 'signing'
7
7
 
8
8
  def packageJson = new JsonSlurper().parse(file("../package.json"))
9
9
  def mavenGroupId = 'ai.glassly'
10
- def mavenPublishVersion = (findProperty('glasslyMavenVersion') ?: System.getenv('MAVEN_VERSION') ?: packageJson.version).toString()
11
- def publicMavenSdk = (findProperty('glasslyPublicSdk') ?: System.getenv('PUBLIC_SDK') ?: 'false').toString().toBoolean()
10
+ def mavenPublishVersion = (findProperty('mavenVersion') ?: System.getenv('MAVEN_VERSION') ?: packageJson.version).toString()
11
+ def publicMavenSdk = (findProperty('publicSdk') ?: System.getenv('PUBLIC_SDK') ?: 'false').toString().toBoolean()
12
12
 
13
13
  // Download Sherpa-ONNX Android AAR from the upstream GitHub release and stage
14
14
  // it as a Maven artifact in a tiny on-disk repository under libs/maven/.
@@ -290,13 +290,13 @@ dependencies {
290
290
  // through the public SDK API. Public Maven artifacts compile against them
291
291
  // without publishing them as runtime transitives, so consumers only need
292
292
  // Maven Central/Google repositories for normal SDK use.
293
- def optionalGlasslyIntegrationDependency = publicMavenSdk ? 'compileOnly' : 'implementation'
293
+ def optionalIntegrationDependency = publicMavenSdk ? 'compileOnly' : 'implementation'
294
294
 
295
295
  // Sherpa-ONNX for local transcription. Jitpack's v1.13.2 build failed
296
296
  // upstream, so we stage the prebuilt AAR into libs/maven/ (see top of file)
297
297
  // and consume it as a Maven coord — AGP rejects raw files() .aar deps in
298
298
  // library modules.
299
- dependencies.add(optionalGlasslyIntegrationDependency, "${sherpaOnnxGroup}:${sherpaOnnxArtifact}:${sherpaOnnxVersion}@aar")
299
+ dependencies.add(optionalIntegrationDependency, "${sherpaOnnxGroup}:${sherpaOnnxArtifact}:${sherpaOnnxVersion}@aar")
300
300
 
301
301
  if (!publicMavenSdk) {
302
302
  // Silero VAD — vendored source under silero/, wired as :silero
@@ -318,8 +318,8 @@ dependencies {
318
318
  compileOnly 'com.squareup.picasso:picasso:2.8'
319
319
  }
320
320
 
321
- implementation 'com.google.protobuf:protobuf-java:4.32.0'
322
- implementation 'com.google.protobuf:protobuf-java-util:4.32.0'
321
+ implementation 'com.google.protobuf:protobuf-java:4.36.1'
322
+ implementation 'com.google.protobuf:protobuf-java-util:4.36.1'
323
323
  }
324
324
 
325
325
  // Pin onnxruntime-android to 1.27.0 — the version sherpa-onnx 1.13.4 was built
@@ -386,7 +386,7 @@ afterEvaluate {
386
386
  repositories {
387
387
  maven {
388
388
  name = 'sonatypeCentral'
389
- def repositoryUrl = System.getenv('GLASSLY_MAVEN_STAGING_REPOSITORY_URL') ?: 'https://ossrh-staging-api.central.sonatype.com/service/local/staging/deploy/maven2/'
389
+ def repositoryUrl = System.getenv('MAVEN_STAGING_REPOSITORY_URL') ?: 'https://ossrh-staging-api.central.sonatype.com/service/local/staging/deploy/maven2/'
390
390
  url = uri(repositoryUrl)
391
391
  if (repositoryUrl.startsWith('http')) {
392
392
  credentials {
@@ -8,7 +8,7 @@ plugins {
8
8
 
9
9
  def packageJson = new JsonSlurper().parse(file("../../package.json"))
10
10
  def mavenGroupId = 'ai.glassly'
11
- def mavenPublishVersion = (findProperty('glasslyMavenVersion') ?: System.getenv('MAVEN_VERSION') ?: packageJson.version).toString()
11
+ def mavenPublishVersion = (findProperty('mavenVersion') ?: System.getenv('MAVEN_VERSION') ?: packageJson.version).toString()
12
12
 
13
13
  group = mavenGroupId
14
14
  version = mavenPublishVersion
@@ -181,7 +181,7 @@ afterEvaluate {
181
181
  repositories {
182
182
  maven {
183
183
  name = 'sonatypeCentral'
184
- def repositoryUrl = System.getenv('GLASSLY_MAVEN_STAGING_REPOSITORY_URL') ?: 'https://ossrh-staging-api.central.sonatype.com/service/local/staging/deploy/maven2/'
184
+ def repositoryUrl = System.getenv('MAVEN_STAGING_REPOSITORY_URL') ?: 'https://ossrh-staging-api.central.sonatype.com/service/local/staging/deploy/maven2/'
185
185
  url = uri(repositoryUrl)
186
186
  if (repositoryUrl.startsWith('http')) {
187
187
  credentials {
@@ -3,9 +3,34 @@
3
3
  The Android library bundles the stock and Glassly CFW EVENOTA images stored in
4
4
  this directory. They are copied from the sibling `g2flash` checkout:
5
5
 
6
- - `g2_2.2.9.22.bin` -> `g2-stock-2.2.9.22.evenota.bin`
7
- - `g2_2.2.9.22_cfw.bin` -> `g2-glassly-cfw-2.2.9.22.evenota.bin`
6
+ - `g2_2.3.0.24.bin` -> `g2-stock-2.3.0.24.evenota.bin`
7
+ - `g2_2.3.0.24_cfw.bin` -> `g2-glassly-cfw-2.3.0.24.evenota.bin`
8
8
 
9
9
  Run `./build_cfw.sh` in `g2flash` before manually replacing the CFW image. The
10
10
  firmware binaries are committed so CI builds bundle them, and verified by size,
11
11
  SHA-256, per-component CRC32C, and the MRAM guard before flashing.
12
+
13
+ The bundled CFW is **GLASSLYCFW/37**, built in `tetramo-labs/g2flash` at
14
+ the commit named in its README, on stock G2 **2.3.0.24** (upstream
15
+ jimrandomh/g2flash's new base with its audited stock ABI table). Every stock
16
+ address was re-derived for 2.3.0.24; the fork keeps its own ANCS relay,
17
+ phone-selectable fast BLE link and four-microphone relay, and gains upstream's
18
+ ring touch-down and ring-report forwarding. Verified on hardware with the
19
+ g2flash shapes, vector and Bad Apple suites and the fast-link toggle.
20
+ Revision 38 adds the retained object cache (modes 37-41): objects and assets
21
+ with 32-bit ids and versions survive a hidden view, so reopening the dashboard
22
+ is one SHOW of references; the raw texture writes (mode 18) are gone. Verified
23
+ on hardware with the g2flash vector and shapes suites (43/43).
24
+ Field 100 is the bare revision string; clients gate on the number (the app
25
+ requires 39).
26
+
27
+ - Size: **4,661,963 bytes**
28
+ - SHA-256: `4c7bf9a552df1a42f26b3378425c7c2121ca4058fed7283ec863c3f8d2cd13fe`
29
+
30
+ Keep both platform copies and their `G2FirmwareCatalog` size/hash pins in sync.
31
+
32
+ Revision 39 adds the BLE keyboard bridge (`patches/keyboard.c`): the right
33
+ lens hosts one HID-over-GATT keyboard, driven by the phone over settings
34
+ fields 130 (control) and 131 (events). Verified on hardware from the g2flash
35
+ reference client (pairing, HOGP discovery and live key events); the phone
36
+ path (G2KeyboardBridge) is not yet hardware-validated. The app requires 39.
@@ -505,8 +505,19 @@ class BluetoothSdk private constructor(
505
505
  deviceManager.showNotificationsPanel()
506
506
  }
507
507
 
508
+ /** Apply on-glass notification behaviour. Throws when nothing is connected. */
509
+ fun setGlassesNotificationConfig(config: GlassesNotificationConfig) {
510
+ deviceManager.setGlassesNotificationConfig(config)
511
+ }
512
+
513
+ fun getGlassesNotificationStatus(): GlassesNotificationStatus =
514
+ deviceManager.glassesNotificationStatus()
515
+
508
516
  // ---------- Native navigation (glasses-rendered nav UI) ----------
509
517
 
518
+ suspend fun glassesSensorControl(sensor: String, operation: String, options: Map<String, Double>): Boolean =
519
+ deviceManager.glassesSensorControl(sensor, operation, options)
520
+
510
521
  fun navigationModeStart() {
511
522
  deviceManager.navigationModeStart()
512
523
  }
@@ -1226,6 +1237,33 @@ class BluetoothSdk private constructor(
1226
1237
  return deviceManager.startG2FirmwareFlash(target)
1227
1238
  }
1228
1239
 
1240
+ // ---------- BLE keyboard bridge (G2 Glassly CFW revision 39+) ----------
1241
+
1242
+ suspend fun g2KeyboardScan(): List<Map<String, Any>> {
1243
+ requireGlassesConnected("scan for a keyboard")
1244
+ return deviceManager.g2KeyboardScan()
1245
+ }
1246
+
1247
+ suspend fun g2KeyboardConnect(address: String, addressType: Int, name: String) {
1248
+ requireGlassesConnected("connect a keyboard")
1249
+ deviceManager.g2KeyboardConnect(address, addressType, name)
1250
+ }
1251
+
1252
+ suspend fun g2KeyboardReconnect() {
1253
+ requireGlassesConnected("reconnect the keyboard")
1254
+ deviceManager.g2KeyboardReconnect()
1255
+ }
1256
+
1257
+ suspend fun g2KeyboardDisconnect() = deviceManager.g2KeyboardDisconnect()
1258
+
1259
+ suspend fun g2KeyboardForget() = deviceManager.g2KeyboardForget()
1260
+
1261
+ suspend fun g2KeyboardPasskey(pin: Int) = deviceManager.g2KeyboardPasskey(pin)
1262
+
1263
+ suspend fun g2KeyboardCompare(accept: Boolean) = deviceManager.g2KeyboardCompare(accept)
1264
+
1265
+ suspend fun g2KeyboardStatus(): Map<String, Any> = deviceManager.g2KeyboardStatus()
1266
+
1229
1267
  /** Select the HTTP(S) manifest used by subsequent OTA checks and installs. */
1230
1268
  fun setOtaVersionUrl(otaVersionUrl: String) {
1231
1269
  configuredOtaVersionUrl = OtaManifestChecker.normalizeHttpUrl(otaVersionUrl)
@@ -6,6 +6,7 @@ import androidx.lifecycle.Lifecycle
6
6
  import androidx.lifecycle.LifecycleOwner
7
7
  import com.glassly.bluetoothsdk.debug.BleTraceLogger
8
8
  import com.glassly.bluetoothsdk.speakerid.SpeakerIdEngine
9
+ import com.glassly.bluetoothsdk.stt.STTTools
9
10
  import com.glassly.bluetoothsdk.utils.DeviceTypes
10
11
  import com.glassly.bluetoothsdk.utils.audio.PcmStreamManager
11
12
  import expo.modules.kotlin.exception.CodedException
@@ -331,10 +332,12 @@ class BluetoothSdkModule : Module() {
331
332
  "button_press",
332
333
  "touch_event",
333
334
  "accel_event",
335
+ "light_sensor",
334
336
  "compass_heading",
335
337
  "compass_calibration",
336
338
  "navigation_mode_change",
337
339
  "navigation_view_change",
340
+ "scene_frame_presented",
338
341
  "head_up",
339
342
  "voice_activity_detection_status",
340
343
  "speaking_status",
@@ -374,6 +377,8 @@ class BluetoothSdkModule : Module() {
374
377
  "speaker_enroll_result",
375
378
  "phone_notification",
376
379
  "phone_notification_dismissed",
380
+ "glasses_notification_status",
381
+ "notification_upload_result",
377
382
  "ws_text",
378
383
  "ws_bin",
379
384
  "mic_pcm",
@@ -394,6 +399,9 @@ class BluetoothSdkModule : Module() {
394
399
  "miniapp_selected",
395
400
  "captions_tester_incident",
396
401
  "extraction_progress",
402
+ "keyboard_key",
403
+ "keyboard_advert",
404
+ "keyboard_pairing",
397
405
  )
398
406
 
399
407
  OnCreate {
@@ -553,9 +561,27 @@ class BluetoothSdkModule : Module() {
553
561
 
554
562
  SdkAsyncFunction("showNativeNotificationPanel") { -> sdk?.showNotificationsPanel() }
555
563
 
564
+ SdkAsyncFunction("setGlassesNotificationConfig") { config: Map<String, Any> ->
565
+ sdk?.setGlassesNotificationConfig(GlassesNotificationConfig.fromMap(config))
566
+ }
567
+
568
+ SdkAsyncFunction("getGlassesNotificationStatus") { ->
569
+ (sdk?.getGlassesNotificationStatus() ?: GlassesNotificationStatus.unsupported()).toMap()
570
+ }
571
+
572
+ AsyncFunction("uploadPhoneNotification") { upload: Map<String, Any> ->
573
+ val parsed = PhoneNotificationUpload.fromMap(upload)
574
+ Bridge.log("MOD: uploadPhoneNotification package=${parsed.packageName}")
575
+ deviceManager?.uploadPhoneNotification(parsed)
576
+ ?: throw IllegalStateException("Glasses are not connected")
577
+ }
578
+
556
579
  // Native navigation mode (glasses-rendered nav UI — G2 today). The JS layer
557
580
  // feature-detects these by name (NativeNavigationService.isSupported), so the
558
581
  // export names must match iOS exactly.
582
+ SdkCoroutineFunction("glassesSensorControl") { sensor: String, operation: String, options: Map<String, Double> ->
583
+ sdk?.glassesSensorControl(sensor, operation, options) ?: false
584
+ }
559
585
  SdkAsyncFunction("navigationModeStart") { -> sdk?.navigationModeStart() }
560
586
  SdkAsyncFunction("navigationModeStop") { arrived: Boolean -> sdk?.navigationModeStop(arrived) }
561
587
  SdkAsyncFunction("navigationUpdateInfo") { params: Map<String, Any?> ->
@@ -570,14 +596,11 @@ class BluetoothSdkModule : Module() {
570
596
 
571
597
  AsyncFunction("ping") { deviceManager?.ping() }
572
598
 
573
- AsyncFunction("dbg1") {
574
- deviceManager?.dbg1()
575
- deviceManager?.sgc?.dbg1()
576
- }
577
- AsyncFunction("dbg2") {
578
- deviceManager?.dbg2()
579
- deviceManager?.sgc?.dbg2()
580
- }
599
+ // DeviceManager forwards to the active SGC; calling sgc here too fired each hook twice.
600
+ AsyncFunction("dbg1") { deviceManager?.dbg1() }
601
+ AsyncFunction("dbg2") { deviceManager?.dbg2() }
602
+ AsyncFunction("dbg3") { deviceManager?.dbg3() }
603
+ AsyncFunction("dbg4") { deviceManager?.dbg4() }
581
604
 
582
605
  // Stub on Android — iOS uses this for the jetsam stress test.
583
606
  Function("getMemoryMB") { -> 0.0 }
@@ -762,6 +785,19 @@ class BluetoothSdkModule : Module() {
762
785
  withContext(Dispatchers.IO) { requireSdk().startG2FirmwareFlash(target) }
763
786
  }
764
787
 
788
+ // MARK: - BLE keyboard bridge (G2 Glassly CFW revision 39+)
789
+
790
+ SdkCoroutineFunction("g2KeyboardScan") { -> requireSdk().g2KeyboardScan() }
791
+ SdkCoroutineFunction("g2KeyboardConnect") { address: String, addressType: Int, name: String ->
792
+ requireSdk().g2KeyboardConnect(address, addressType, name)
793
+ }
794
+ SdkCoroutineFunction("g2KeyboardReconnect") { -> requireSdk().g2KeyboardReconnect() }
795
+ SdkCoroutineFunction("g2KeyboardDisconnect") { -> requireSdk().g2KeyboardDisconnect() }
796
+ SdkCoroutineFunction("g2KeyboardForget") { -> requireSdk().g2KeyboardForget() }
797
+ SdkCoroutineFunction("g2KeyboardPasskey") { pin: Int -> requireSdk().g2KeyboardPasskey(pin) }
798
+ SdkCoroutineFunction("g2KeyboardCompare") { accept: Boolean -> requireSdk().g2KeyboardCompare(accept) }
799
+ SdkCoroutineFunction("g2KeyboardStatus") { -> requireSdk().g2KeyboardStatus() }
800
+
765
801
  // MARK: - Power Control Commands
766
802
 
767
803
  SdkAsyncFunction("sendShutdown") { -> sdk?.sendShutdown() }
@@ -840,7 +876,7 @@ class BluetoothSdkModule : Module() {
840
876
  // does a synchronous JNI model reload that would otherwise block every other
841
877
  // native call in the app until it completes.
842
878
  AsyncFunction("restartTranscriber") Coroutine { ->
843
- withContext(Dispatchers.IO) { deviceManager?.restartTranscriber() }
879
+ withContext(Dispatchers.IO) { checkNotNull(deviceManager) { "Bluetooth SDK is not initialized" }.restartTranscriber() }
844
880
  }
845
881
 
846
882
  // MARK: - Speaker Identification
@@ -948,7 +984,7 @@ class BluetoothSdkModule : Module() {
948
984
  appContext.reactContext
949
985
  ?: appContext.currentActivity
950
986
  ?: throw IllegalStateException("No context available")
951
- com.glassly.bluetoothsdk.stt.STTTools.setSttModelDetails(context, path, languageCode)
987
+ STTTools.setSttModelDetails(context, path, languageCode)
952
988
  }
953
989
 
954
990
  AsyncFunction("getSttModelPath") { ->
@@ -956,7 +992,7 @@ class BluetoothSdkModule : Module() {
956
992
  appContext.reactContext
957
993
  ?: appContext.currentActivity
958
994
  ?: throw IllegalStateException("No context available")
959
- com.glassly.bluetoothsdk.stt.STTTools.getSttModelPath(context)
995
+ STTTools.getSttModelPath(context)
960
996
  }
961
997
 
962
998
  AsyncFunction("checkSttModelAvailable") { ->
@@ -964,11 +1000,11 @@ class BluetoothSdkModule : Module() {
964
1000
  appContext.reactContext
965
1001
  ?: appContext.currentActivity
966
1002
  ?: throw IllegalStateException("No context available")
967
- com.glassly.bluetoothsdk.stt.STTTools.checkSTTModelAvailable(context)
1003
+ STTTools.checkSTTModelAvailable(context)
968
1004
  }
969
1005
 
970
1006
  AsyncFunction("validateSttModel") { path: String ->
971
- com.glassly.bluetoothsdk.stt.STTTools.validateSTTModel(path)
1007
+ STTTools.validateSTTModel(path)
972
1008
  }
973
1009
 
974
1010
  // Runs on Dispatchers.IO, not the shared Expo AsyncFunctionQueue: bz2/tar
@@ -976,7 +1012,7 @@ class BluetoothSdkModule : Module() {
976
1012
  // shared queue it froze every other native call in the app until it finished.
977
1013
  AsyncFunction("extractTarBz2") Coroutine { sourcePath: String, destinationPath: String ->
978
1014
  withContext(Dispatchers.IO) {
979
- com.glassly.bluetoothsdk.stt.STTTools.extractTarBz2(sourcePath, destinationPath)
1015
+ STTTools.extractTarBz2(sourcePath, destinationPath)
980
1016
  }
981
1017
  }
982
1018
 
@@ -172,6 +172,24 @@ public class Bridge private constructor() {
172
172
  sendTypedMessage("speaker_enroll_result", body as Map<String, Any>)
173
173
  }
174
174
 
175
+ /** display.render() frame outcome; seq 0 frames are native-originated and never reported. */
176
+ @JvmStatic
177
+ fun sendSceneFramePresented(seq: Int, appId: String, presented: Boolean, reason: String?) {
178
+ if (seq <= 0) return
179
+ val body = HashMap<String, Any>()
180
+ body["seq"] = seq
181
+ body["appId"] = appId
182
+ body["presented"] = presented
183
+ if (reason != null) body["reason"] = reason
184
+ sendTypedMessage("scene_frame_presented", body as Map<String, Any>)
185
+ }
186
+
187
+ /** The glasses finished every animation a tagged scene commit started (tag = frame seq & 0xFFFF). */
188
+ @JvmStatic
189
+ fun sendSceneSettled(tag: Int) {
190
+ sendTypedMessage("scene_settled", mapOf("tag" to tag))
191
+ }
192
+
175
193
  /** Send head position event */
176
194
  @JvmStatic
177
195
  fun sendHeadUp(isUp: Boolean) {