@mentra/bluetooth-sdk 0.1.0

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 (278) hide show
  1. package/README.md +278 -0
  2. package/android/build.gradle +167 -0
  3. package/android/lc3Lib/CMakeLists.txt +6 -0
  4. package/android/lc3Lib/build.gradle +109 -0
  5. package/android/lc3Lib/proguard-rules.pro +33 -0
  6. package/android/lc3Lib/src/main/AndroidManifest.xml +2 -0
  7. package/android/lc3Lib/src/main/cpp/CMakeLists.txt +8 -0
  8. package/android/lc3Lib/src/main/cpp/google_opus_stuff/CMakeLists.txt +30 -0
  9. package/android/lc3Lib/src/main/cpp/google_opus_stuff/jni/CMakeLists.txt +7 -0
  10. package/android/lc3Lib/src/main/cpp/google_opus_stuff/jni/ogg_opus_encoder.cc +96 -0
  11. package/android/lc3Lib/src/main/cpp/google_opus_stuff/jni/ogg_opus_encoder.h +53 -0
  12. package/android/lc3Lib/src/main/cpp/google_opus_stuff/libogg/Makefile.am +6 -0
  13. package/android/lc3Lib/src/main/cpp/google_opus_stuff/libogg/config_types.h +26 -0
  14. package/android/lc3Lib/src/main/cpp/google_opus_stuff/libogg/config_types.h.in +26 -0
  15. package/android/lc3Lib/src/main/cpp/google_opus_stuff/libogg/ogg.h +209 -0
  16. package/android/lc3Lib/src/main/cpp/google_opus_stuff/libogg/os_types.h +158 -0
  17. package/android/lc3Lib/src/main/cpp/google_opus_stuff/libopus/opus.h +981 -0
  18. package/android/lc3Lib/src/main/cpp/google_opus_stuff/libopus/opus_custom.h +342 -0
  19. package/android/lc3Lib/src/main/cpp/google_opus_stuff/libopus/opus_defines.h +799 -0
  20. package/android/lc3Lib/src/main/cpp/google_opus_stuff/libopus/opus_multistream.h +660 -0
  21. package/android/lc3Lib/src/main/cpp/google_opus_stuff/libopus/opus_projection.h +568 -0
  22. package/android/lc3Lib/src/main/cpp/google_opus_stuff/libopus/opus_types.h +166 -0
  23. package/android/lc3Lib/src/main/cpp/google_opus_stuff/ogg_opus_encoder.cc +268 -0
  24. package/android/lc3Lib/src/main/cpp/google_opus_stuff/ogg_opus_encoder.h +115 -0
  25. package/android/lc3Lib/src/main/cpp/google_opus_stuff/opus_tools/opus_header.h +59 -0
  26. package/android/lc3Lib/src/main/cpp/liblc3/CMakeLists.txt +38 -0
  27. package/android/lc3Lib/src/main/cpp/liblc3/include/lc3.h +309 -0
  28. package/android/lc3Lib/src/main/cpp/liblc3/include/lc3_private.h +162 -0
  29. package/android/lc3Lib/src/main/cpp/liblc3/include/rnnoise.h +114 -0
  30. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/CMakeLists.txt +19 -0
  31. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/attdet.c +92 -0
  32. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/attdet.h +44 -0
  33. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/bits.c +375 -0
  34. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/bits.h +315 -0
  35. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/bwdet.c +129 -0
  36. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/bwdet.h +69 -0
  37. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/common.h +148 -0
  38. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/energy.c +70 -0
  39. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/energy.h +43 -0
  40. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/fastmath.h +158 -0
  41. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/lc3.c +702 -0
  42. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/ltpf.c +893 -0
  43. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/ltpf.h +111 -0
  44. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/ltpf_arm.h +506 -0
  45. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/ltpf_neon.h +281 -0
  46. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/makefile.mk +35 -0
  47. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/mdct.c +452 -0
  48. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/mdct.h +57 -0
  49. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/mdct_neon.h +296 -0
  50. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/meson.build +46 -0
  51. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/plc.c +61 -0
  52. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/plc.h +57 -0
  53. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/sns.c +880 -0
  54. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/sns.h +103 -0
  55. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/spec.c +904 -0
  56. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/spec.h +119 -0
  57. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/tables.c +3457 -0
  58. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/tables.h +94 -0
  59. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/tns.c +457 -0
  60. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/tns.h +99 -0
  61. package/android/lc3Lib/src/main/cpp/liblc3/liblc3.cpp +159 -0
  62. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/_kiss_fft_guts.h +182 -0
  63. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/arch.h +261 -0
  64. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/celt_lpc.c +279 -0
  65. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/celt_lpc.h +59 -0
  66. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/common.h +48 -0
  67. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/compile.sh +3 -0
  68. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/denoise.c +646 -0
  69. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/kiss_fft.c +601 -0
  70. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/kiss_fft.h +203 -0
  71. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/opus_types.h +159 -0
  72. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/pitch.c +526 -0
  73. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/pitch.h +149 -0
  74. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/rnn.c +178 -0
  75. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/rnn.h +69 -0
  76. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/rnn_data.c +11051 -0
  77. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/rnn_data.h +34 -0
  78. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/rnn_reader.c +168 -0
  79. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/rnn_train.py +66 -0
  80. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/tansig_table.h +45 -0
  81. package/android/lc3Lib/src/main/java/com/mentra/lc3Lib/Lc3Cpp.java +38 -0
  82. package/android/lc3Lib/third_party/CMakeLists.txt +23 -0
  83. package/android/lc3Lib/third_party/CMakeLists_libogg.txt +18 -0
  84. package/android/lc3Lib/third_party/CMakeLists_libopus.txt +18 -0
  85. package/android/lc3Lib/third_party/CMakeLists_opus-tools.txt +23 -0
  86. package/android/lc3Lib/third_party/opus_tools/src/AUTHORS +5 -0
  87. package/android/lc3Lib/third_party/opus_tools/src/CMakeLists.txt +4 -0
  88. package/android/lc3Lib/third_party/opus_tools/src/COPYING +371 -0
  89. package/android/lc3Lib/third_party/opus_tools/src/ChangeLog +3 -0
  90. package/android/lc3Lib/third_party/opus_tools/src/Makefile.am +42 -0
  91. package/android/lc3Lib/third_party/opus_tools/src/Makefile.unix +23 -0
  92. package/android/lc3Lib/third_party/opus_tools/src/NEWS +0 -0
  93. package/android/lc3Lib/third_party/opus_tools/src/README +0 -0
  94. package/android/lc3Lib/third_party/opus_tools/src/autogen.sh +115 -0
  95. package/android/lc3Lib/third_party/opus_tools/src/configure.ac +293 -0
  96. package/android/lc3Lib/third_party/opus_tools/src/src/arch.h +239 -0
  97. package/android/lc3Lib/third_party/opus_tools/src/src/audio-in.c +1046 -0
  98. package/android/lc3Lib/third_party/opus_tools/src/src/diag_range.c +245 -0
  99. package/android/lc3Lib/third_party/opus_tools/src/src/diag_range.h +28 -0
  100. package/android/lc3Lib/third_party/opus_tools/src/src/info_opus.c +320 -0
  101. package/android/lc3Lib/third_party/opus_tools/src/src/info_opus.h +51 -0
  102. package/android/lc3Lib/third_party/opus_tools/src/src/lpc.c +157 -0
  103. package/android/lc3Lib/third_party/opus_tools/src/src/lpc.h +27 -0
  104. package/android/lc3Lib/third_party/opus_tools/src/src/opus_header.c +286 -0
  105. package/android/lc3Lib/third_party/opus_tools/src/src/opus_header.h +59 -0
  106. package/android/lc3Lib/third_party/opus_tools/src/src/opusdec.c +884 -0
  107. package/android/lc3Lib/third_party/opus_tools/src/src/opusenc.c +1021 -0
  108. package/android/lc3Lib/third_party/opus_tools/src/src/opusenc.h +101 -0
  109. package/android/lc3Lib/third_party/opus_tools/src/src/opusinfo.c +639 -0
  110. package/android/lc3Lib/third_party/opus_tools/src/src/opusinfo.h +51 -0
  111. package/android/lc3Lib/third_party/opus_tools/src/src/os_support.h +167 -0
  112. package/android/lc3Lib/third_party/opus_tools/src/src/resample.c +1137 -0
  113. package/android/lc3Lib/third_party/opus_tools/src/src/speex_resampler.h +344 -0
  114. package/android/lc3Lib/third_party/opus_tools/src/src/stack_alloc.h +115 -0
  115. package/android/lc3Lib/third_party/opus_tools/src/src/wav_io.c +125 -0
  116. package/android/lc3Lib/third_party/opus_tools/src/src/wav_io.h +62 -0
  117. package/android/lc3Lib/third_party/opus_tools/src/src/wave_out.c +223 -0
  118. package/android/lc3Lib/third_party/opus_tools/src/src/wave_out.h +60 -0
  119. package/android/settings.gradle +1 -0
  120. package/android/src/main/AndroidManifest.xml +40 -0
  121. package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +528 -0
  122. package/android/src/main/java/com/mentra/bluetoothsdk/Bridge.kt +610 -0
  123. package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +1616 -0
  124. package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +314 -0
  125. package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothModels.kt +1710 -0
  126. package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +554 -0
  127. package/android/src/main/java/com/mentra/bluetoothsdk/ObservableStore.kt +68 -0
  128. package/android/src/main/java/com/mentra/bluetoothsdk/controllers/ControllerManager.kt +158 -0
  129. package/android/src/main/java/com/mentra/bluetoothsdk/controllers/R1.kt +934 -0
  130. package/android/src/main/java/com/mentra/bluetoothsdk/services/Foreground.kt +175 -0
  131. package/android/src/main/java/com/mentra/bluetoothsdk/services/PhoneMic.kt +1109 -0
  132. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G1.java +3974 -0
  133. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G2.kt +3551 -0
  134. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Mach1.java +1327 -0
  135. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.java +6959 -0
  136. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraNex.kt +1655 -0
  137. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraosBle.java +53675 -0
  138. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/SGCManager.kt +202 -0
  139. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Simulated.kt +224 -0
  140. package/android/src/main/java/com/mentra/bluetoothsdk/stt/STTTools.kt +340 -0
  141. package/android/src/main/java/com/mentra/bluetoothsdk/stt/SherpaOnnxTranscriber.kt +465 -0
  142. package/android/src/main/java/com/mentra/bluetoothsdk/stt/VadGateSpeechPolicy.kt +221 -0
  143. package/android/src/main/java/com/mentra/bluetoothsdk/utils/AES.java +106 -0
  144. package/android/src/main/java/com/mentra/bluetoothsdk/utils/AudioSessionMonitor.kt +189 -0
  145. package/android/src/main/java/com/mentra/bluetoothsdk/utils/BitmapJavaUtils.java +169 -0
  146. package/android/src/main/java/com/mentra/bluetoothsdk/utils/BlePhotoUploadService.java +179 -0
  147. package/android/src/main/java/com/mentra/bluetoothsdk/utils/Constants.kt +44 -0
  148. package/android/src/main/java/com/mentra/bluetoothsdk/utils/G1Text.kt +436 -0
  149. package/android/src/main/java/com/mentra/bluetoothsdk/utils/IncidentLogBleRelayNaming.java +29 -0
  150. package/android/src/main/java/com/mentra/bluetoothsdk/utils/IncidentLogBleUploadService.java +76 -0
  151. package/android/src/main/java/com/mentra/bluetoothsdk/utils/K900ProtocolUtils.java +791 -0
  152. package/android/src/main/java/com/mentra/bluetoothsdk/utils/MessageChunker.java +186 -0
  153. package/android/src/main/java/com/mentra/bluetoothsdk/utils/NexSGCUtils.kt +589 -0
  154. package/android/src/main/java/com/mentra/bluetoothsdk/utils/PhoneAudioMonitor.kt +303 -0
  155. package/android/src/main/java/com/mentra/bluetoothsdk/utils/SmartGlassesConnectionState.java +9 -0
  156. package/android/src/main/java/com/mentra/bluetoothsdk/utils/audio/ByteUtilAudioPlayer.java +655 -0
  157. package/android/src/main/java/com/mentra/bluetoothsdk/utils/audio/Lc3Player.java +441 -0
  158. package/android/src/main/java/com/mentra/bluetoothsdk/utils/audio/PCMAudioPlayer.java +431 -0
  159. package/android/src/main/res/values/strings.xml +4 -0
  160. package/app.plugin.js +1 -0
  161. package/build/BluetoothSdk.types.d.ts +498 -0
  162. package/build/BluetoothSdk.types.d.ts.map +1 -0
  163. package/build/BluetoothSdk.types.js +23 -0
  164. package/build/BluetoothSdk.types.js.map +1 -0
  165. package/build/BluetoothSdkModule.d.ts +71 -0
  166. package/build/BluetoothSdkModule.d.ts.map +1 -0
  167. package/build/BluetoothSdkModule.js +106 -0
  168. package/build/BluetoothSdkModule.js.map +1 -0
  169. package/build/index.d.ts +3 -0
  170. package/build/index.d.ts.map +1 -0
  171. package/build/index.js +5 -0
  172. package/build/index.js.map +1 -0
  173. package/expo-module.config.json +9 -0
  174. package/ios/BluetoothSdkModule.swift +665 -0
  175. package/ios/MentraBluetoothSDK.podspec +77 -0
  176. package/ios/Packages/CoreObjC/CoreObjC.xcodeproj/project.pbxproj +213 -0
  177. package/ios/Packages/CoreObjC/PcmConverter.h +22 -0
  178. package/ios/Packages/CoreObjC/PcmConverter.m +266 -0
  179. package/ios/Packages/CoreObjC/attdet.c +92 -0
  180. package/ios/Packages/CoreObjC/attdet.h +44 -0
  181. package/ios/Packages/CoreObjC/bits.c +375 -0
  182. package/ios/Packages/CoreObjC/bits.h +315 -0
  183. package/ios/Packages/CoreObjC/bwdet.c +129 -0
  184. package/ios/Packages/CoreObjC/bwdet.h +69 -0
  185. package/ios/Packages/CoreObjC/common.h +151 -0
  186. package/ios/Packages/CoreObjC/energy.c +70 -0
  187. package/ios/Packages/CoreObjC/energy.h +43 -0
  188. package/ios/Packages/CoreObjC/fastmath.h +158 -0
  189. package/ios/Packages/CoreObjC/lc3.c +704 -0
  190. package/ios/Packages/CoreObjC/lc3.h +313 -0
  191. package/ios/Packages/CoreObjC/lc3_cpp.h +283 -0
  192. package/ios/Packages/CoreObjC/lc3_private.h +163 -0
  193. package/ios/Packages/CoreObjC/ltpf.c +905 -0
  194. package/ios/Packages/CoreObjC/ltpf.h +111 -0
  195. package/ios/Packages/CoreObjC/ltpf_arm.h +506 -0
  196. package/ios/Packages/CoreObjC/ltpf_neon.h +281 -0
  197. package/ios/Packages/CoreObjC/makefile.mk +35 -0
  198. package/ios/Packages/CoreObjC/mdct.c +469 -0
  199. package/ios/Packages/CoreObjC/mdct.h +57 -0
  200. package/ios/Packages/CoreObjC/mdct_neon.h +296 -0
  201. package/ios/Packages/CoreObjC/meson.build +61 -0
  202. package/ios/Packages/CoreObjC/plc.c +61 -0
  203. package/ios/Packages/CoreObjC/plc.h +57 -0
  204. package/ios/Packages/CoreObjC/rnnoise.h +114 -0
  205. package/ios/Packages/CoreObjC/sns.c +880 -0
  206. package/ios/Packages/CoreObjC/sns.h +103 -0
  207. package/ios/Packages/CoreObjC/spec.c +907 -0
  208. package/ios/Packages/CoreObjC/spec.h +119 -0
  209. package/ios/Packages/CoreObjC/tables.c +3457 -0
  210. package/ios/Packages/CoreObjC/tables.h +94 -0
  211. package/ios/Packages/CoreObjC/tns.c +457 -0
  212. package/ios/Packages/CoreObjC/tns.h +99 -0
  213. package/ios/Packages/SherpaOnnx/Model/joiner.onnx +0 -0
  214. package/ios/Packages/SherpaOnnx/Model/tokens.txt +502 -0
  215. package/ios/Packages/SherpaOnnx/SherpaOnnx.swift +1659 -0
  216. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/Headers/cargs.h +162 -0
  217. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/Headers/sherpa-onnx/c-api/c-api.h +1852 -0
  218. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/Headers/sherpa-onnx/c-api/cxx-api.h +674 -0
  219. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/Info.plist +44 -0
  220. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64/libsherpa-onnx.a +0 -0
  221. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64/sherpa-onnx.a +0 -0
  222. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64_x86_64-simulator/libsherpa-onnx.a +0 -0
  223. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64_x86_64-simulator/sherpa-onnx.a +0 -0
  224. package/ios/Packages/VAD/Common/VAD/VADQuality.swift +41 -0
  225. package/ios/Packages/VAD/Common/VAD/VADState.swift +26 -0
  226. package/ios/Packages/VAD/Common/VAD/VADStrategy.swift +29 -0
  227. package/ios/Packages/VAD/Common/VAD/VADType.swift +14 -0
  228. package/ios/Packages/VAD/Data/Configuration.swift +68 -0
  229. package/ios/Packages/VAD/Data/FrameSize.swift +39 -0
  230. package/ios/Packages/VAD/Data/Record.swift +13 -0
  231. package/ios/Packages/VAD/Data/Result.swift +22 -0
  232. package/ios/Packages/VAD/Data/SampleRate.swift +48 -0
  233. package/ios/Packages/VAD/Silero/Model/silero_vad.onnx +0 -0
  234. package/ios/Packages/VAD/Silero/SileroVAD.swift +284 -0
  235. package/ios/Packages/VAD/Silero/SileroVADStrategy.swift +64 -0
  236. package/ios/Packages/libbz2/module.modulemap +5 -0
  237. package/ios/Packages/libbz2/shim.h +1 -0
  238. package/ios/Source/Bridge.swift +353 -0
  239. package/ios/Source/Bridging-Header.h +16 -0
  240. package/ios/Source/DeviceManager.swift +1478 -0
  241. package/ios/Source/DeviceStore.swift +295 -0
  242. package/ios/Source/MentraBluetoothSDK.swift +2439 -0
  243. package/ios/Source/ObservableStore.swift +88 -0
  244. package/ios/Source/PrivacyInfo.xcprivacy +23 -0
  245. package/ios/Source/controllers/ControllerManager.swift +187 -0
  246. package/ios/Source/controllers/R1.swift +827 -0
  247. package/ios/Source/services/PhoneMic.swift +662 -0
  248. package/ios/Source/sgcs/Frame.swift +651 -0
  249. package/ios/Source/sgcs/G1.swift +2505 -0
  250. package/ios/Source/sgcs/G2.swift +3730 -0
  251. package/ios/Source/sgcs/Mach1.swift +543 -0
  252. package/ios/Source/sgcs/MentraLive.swift +4708 -0
  253. package/ios/Source/sgcs/MentraNex.swift +2479 -0
  254. package/ios/Source/sgcs/SGCManager.swift +243 -0
  255. package/ios/Source/sgcs/Simulated.swift +270 -0
  256. package/ios/Source/sgcs/mentraos_ble.pb.swift +3811 -0
  257. package/ios/Source/stt/STTTools.swift +154 -0
  258. package/ios/Source/stt/SherpaOnnxTranscriber.swift +401 -0
  259. package/ios/Source/utils/AudioSessionMonitor.swift +283 -0
  260. package/ios/Source/utils/Constants.swift +74 -0
  261. package/ios/Source/utils/Enums.swift +95 -0
  262. package/ios/Source/utils/G1Text.swift +2067 -0
  263. package/ios/Source/utils/JSCExperiment.swift +241 -0
  264. package/ios/Source/utils/MemoryMonitor.swift +44 -0
  265. package/ios/Source/utils/MessageChunker.swift +164 -0
  266. package/ios/Source/utils/Models.swift +135 -0
  267. package/ios/Source/utils/PhoneAudioMonitor.swift +226 -0
  268. package/ios/Source/utils/TarBz2Extractor.swift +206 -0
  269. package/package.json +78 -0
  270. package/plugin/build/index.d.ts +6 -0
  271. package/plugin/build/index.js +12 -0
  272. package/plugin/build/withAndroid.d.ts +4 -0
  273. package/plugin/build/withAndroid.js +80 -0
  274. package/plugin/build/withIos.d.ts +4 -0
  275. package/plugin/build/withIos.js +64 -0
  276. package/src/BluetoothSdk.types.ts +581 -0
  277. package/src/BluetoothSdkModule.ts +259 -0
  278. package/src/index.ts +4 -0
@@ -0,0 +1,1655 @@
1
+ package com.mentra.bluetoothsdk.sgcs
2
+
3
+ import com.mentra.bluetoothsdk.DeviceManager
4
+ import com.mentra.bluetoothsdk.DeviceStore
5
+
6
+ import android.graphics.BitmapFactory
7
+ import android.bluetooth.BluetoothAdapter
8
+ import android.bluetooth.BluetoothDevice
9
+ import android.bluetooth.BluetoothGatt
10
+ import android.bluetooth.BluetoothGattCallback
11
+ import android.bluetooth.BluetoothGattDescriptor
12
+ import android.bluetooth.BluetoothGattService
13
+ import android.bluetooth.BluetoothGattCharacteristic
14
+ import android.bluetooth.BluetoothProfile
15
+ import android.bluetooth.le.BluetoothLeScanner
16
+ import android.bluetooth.le.ScanCallback
17
+ import android.bluetooth.le.ScanFilter
18
+ import android.bluetooth.le.ScanResult
19
+ import android.bluetooth.le.ScanSettings
20
+ import android.os.Message
21
+ import android.os.Handler
22
+ import android.os.Looper
23
+ import android.util.Log
24
+ import android.content.Context
25
+
26
+ import mentraos.ble.MentraosBle.GlassesToPhone
27
+ import mentraos.ble.MentraosBle.PhoneToGlasses
28
+ import mentraos.ble.MentraosBle.ChargingState
29
+ import mentraos.ble.MentraosBle.BatteryStatus
30
+ import mentraos.ble.MentraosBle.VersionResponse
31
+ import mentraos.ble.MentraosBle.HeadGesture
32
+ import mentraos.ble.MentraosBle.ButtonEvent
33
+ import mentraos.ble.MentraosBle.ImuData
34
+ import mentraos.ble.MentraosBle.ImageTransferComplete
35
+ import mentraos.ble.MentraosBle.HeadUpAngleResponse
36
+ import mentraos.ble.MentraosBle.HeadPosition
37
+ import mentraos.ble.MentraosBle.DeviceInfo
38
+
39
+ import com.mentra.bluetoothsdk.sgcs.SGCManager
40
+ import com.mentra.bluetoothsdk.Bridge
41
+ import com.mentra.bluetoothsdk.utils.DeviceTypes
42
+ import com.mentra.bluetoothsdk.utils.NexProtobufUtils
43
+ import com.mentra.bluetoothsdk.utils.NexEventUtils
44
+ import com.mentra.bluetoothsdk.utils.NexBluetoothConstants
45
+ import com.mentra.bluetoothsdk.utils.NexDisplayConstants
46
+ import com.mentra.bluetoothsdk.utils.NexBluetoothPacketTypes
47
+ import com.mentra.bluetoothsdk.utils.BitmapJavaUtils
48
+ import com.mentra.bluetoothsdk.utils.G1FontLoaderKt
49
+ import com.mentra.bluetoothsdk.utils.G1Text
50
+ import com.mentra.bluetoothsdk.utils.ConnTypes
51
+ import com.mentra.lc3Lib.Lc3Cpp
52
+ import com.mentra.bluetoothsdk.utils.audio.Lc3Player
53
+
54
+ import java.util.UUID
55
+ import java.util.concurrent.LinkedBlockingQueue
56
+ import java.nio.charset.Charset
57
+ import java.util.Random
58
+
59
+
60
+ import org.json.JSONObject
61
+
62
+ import com.google.protobuf.InvalidProtocolBufferException
63
+
64
+ class MentraNex : SGCManager() {
65
+ companion object {
66
+ private const val TAG = "MentraNex";
67
+
68
+ private const val MTU_517: Int = 517
69
+ private const val MTU_DEFAULT: Int = 23
70
+
71
+ private const val MAX_CHUNK_SIZE_DEFAULT: Int = 176 // Maximum chunk size for BLE packets
72
+ private const val DELAY_BETWEEN_CHUNKS_SEND: Long = 10 // Adjust this value as needed
73
+
74
+ private const val INITIAL_CONNECTION_DELAY_MS = 350L // Adjust this value as needed
75
+ private const val MICBEAT_INTERVAL_MS: Long = (1000 * 60) * 30; // micbeat every 30 minutes
76
+
77
+ private const val MAIN_TASK_HANDLER_CODE_GATT_STATUS_CHANGED: Int = 110
78
+ private const val MAIN_TASK_HANDLER_CODE_DISCOVER_SERVICES: Int = 120
79
+ private const val MAIN_TASK_HANDLER_CODE_CHARACTERISTIC_VALUE_NOTIFIED: Int = 210
80
+
81
+ // actions of device or gatt
82
+ private const val MAIN_TASK_HANDLER_CODE_CONNECT_DEVICE: Int = 310
83
+ private const val MAIN_TASK_HANDLER_CODE_DISCONNECT_DEVICE: Int = 320
84
+ private const val MAIN_TASK_HANDLER_CODE_RECONNECT_DEVICE: Int = 350
85
+ private const val MAIN_TASK_HANDLER_CODE_CANCEL_RECONNECT_DEVICE: Int = 360
86
+ private const val MAIN_TASK_HANDLER_CODE_RECONNECT_GATT: Int = 370
87
+ private const val MAIN_TASK_HANDLER_CODE_SCAN_START: Int = 410
88
+ private const val MAIN_TASK_HANDLER_CODE_SCAN_END: Int = 420
89
+
90
+ // actions of NEX Glasses
91
+ private const val MAIN_TASK_HANDLER_CODE_BATTERY_QUERY: Int = 620
92
+ private const val MAIN_TASK_HANDLER_CODE_HEART_BEAT: Int = 630
93
+ }
94
+
95
+ private var heartbeatCount: Int = 0;
96
+ private var micBeatCount: Int = 0;
97
+
98
+ private var context: Context? = null
99
+ // private var isDebug: Boolean = true
100
+
101
+ private var isLc3AudioEnabled: Boolean = true
102
+ private var lc3AudioPlayer: Lc3Player? = null
103
+
104
+ private var lc3DecoderPtr: Long = 0
105
+
106
+ @Volatile private var isImageSendProgressing: Boolean = false
107
+
108
+ private var mainDevice: BluetoothDevice? = null
109
+ private var bluetoothAdapter: BluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
110
+
111
+ private var isScanningForCompatibleDevices: Boolean = false
112
+
113
+ private var isMainConnected = false
114
+ private var isKilled: Boolean = false
115
+ private var lastConnectionTimestamp: Long = 0
116
+ private var lastSendTimestamp: Long = 0
117
+ private var mainReconnectAttempts: Int = 0
118
+
119
+ private var lastReceivedLc3Sequence: Int = -1
120
+
121
+ private val fontLoader: G1FontLoaderKt = G1FontLoaderKt()
122
+ private val textRenderer: G1Text = G1Text()
123
+ private var preferredMainDeviceId: String? = null
124
+ private var savedNexMainName: String? = null
125
+ private var savedNexMainAddress: String? = null
126
+
127
+ private var mainGlassGatt: BluetoothGatt? = null
128
+ private var mainWriteChar: BluetoothGattCharacteristic? = null
129
+ private var mainNotifyChar: BluetoothGattCharacteristic? = null
130
+ private var currentMTU: Int = 0
131
+ private var deviceMaxMTU: Int = 0
132
+
133
+ private var maxChunkSize: Int = MAX_CHUNK_SIZE_DEFAULT // Maximum chunk size for BLE packets
134
+ private var bmpChunkSize: Int = 194 // BMP chunk size
135
+
136
+ @Volatile private var isWorkerRunning = false
137
+ // Queue to hold pending requests
138
+ private val sendQueue = LinkedBlockingQueue<Array<SendRequest>>()
139
+ private val mainWaiter = BooleanWaiter()
140
+ private val mainServicesWaiter = BooleanWaiter()
141
+
142
+ private var shouldUseGlassesMic: Boolean = false
143
+ private var microphoneStateBeforeDisconnection: Boolean = false
144
+
145
+ private var whiteListedAlready: Boolean = false
146
+ private var isMicrophoneEnabled: Boolean = true
147
+ private var lastThingDisplayedWasAnImage: Boolean = false
148
+ private var isScanning: Boolean = false
149
+
150
+ private var protobufVersionPosted: Boolean = false
151
+
152
+ private var hasConnectedThisSession: Boolean = false
153
+
154
+ private var bleScanCallback: ScanCallback? = null
155
+
156
+ private val modernScanCallback = object : ScanCallback() {
157
+ override fun onScanResult(callbackType: Int, result: ScanResult) {
158
+ val device = result.getDevice()
159
+ val name = device.getName()
160
+
161
+ // Now you can reference the bluetoothAdapter field if needed:
162
+ if (!bluetoothAdapter.isEnabled) {
163
+ Log.e(TAG, "Bluetooth is disabled")
164
+ return
165
+ }
166
+ Bridge.log("=== New Device is Found ===$name ${device.address}")
167
+ Bridge.log("=== New Glasses Device Information ===")
168
+
169
+ // Log all available device information for debugging
170
+ Bridge.log("=== Device Information ===")
171
+ Bridge.log("Device Name: $name")
172
+ Bridge.log("Device Address: ${device.address}")
173
+ Bridge.log("Device Type: ${device.type}")
174
+ Bridge.log("Device Class: ${device.bluetoothClass}")
175
+ Bridge.log("Bond State: ${device.bondState}")
176
+
177
+ if (name == null || (!name.contains("Nex1-") && !name.contains("MENTRA_DISPLAY_"))) {
178
+ return
179
+ }
180
+
181
+ // If we already have saved device names for main...
182
+ if (name != null && preferredMainDeviceId != null) {
183
+ if (!name.contains(preferredMainDeviceId!!)) {
184
+ return // Not a matching device
185
+ }
186
+ }
187
+
188
+ // Identify which side (main)
189
+ stopScan()
190
+ mainDevice = device
191
+ mainTaskHandler.sendEmptyMessageDelayed(MAIN_TASK_HANDLER_CODE_RECONNECT_DEVICE, 0) // 1 second delay
192
+ }
193
+
194
+ override fun onScanFailed(errorCode: Int) {
195
+ Log.e(TAG,"Scan failed with error: $errorCode")
196
+ }
197
+ }
198
+
199
+ init {
200
+ context = Bridge.getContext()
201
+ // isDebug = isDebug(context)
202
+ type = DeviceTypes.NEX
203
+ hasMic = true
204
+ DeviceStore.apply("glasses", "micEnabled", false)
205
+ preferredMainDeviceId = DeviceManager.getInstance().deviceName
206
+
207
+ // Initialize LC3 audio player
208
+ lc3AudioPlayer = Lc3Player(context)
209
+ lc3AudioPlayer?.init()
210
+ if (isLc3AudioEnabled) {
211
+ lc3AudioPlayer?.startPlay()
212
+ }
213
+ }
214
+
215
+ private val random: Random = Random()
216
+
217
+ private val mainGattCallback: BluetoothGattCallback = createGattCallback()
218
+
219
+ private var mainTaskHandler: Handler = Handler(Looper.getMainLooper(), Handler.Callback(::handleMainTaskMessage))
220
+ private var whiteListHandler: Handler = Handler()
221
+ private var micEnableHandler: Handler = Handler()
222
+ private var micBeatHandler: Handler = Handler()
223
+ private var micBeatRunnable: Runnable? = null
224
+ private var notificationHandler: Handler = Handler()
225
+ private var notificationRunnable: Runnable? = null
226
+ private var textWallHandler: Handler = Handler()
227
+ private val textWallRunnable: Runnable? = null
228
+ private var findCompatibleDevicesHandler: Handler? = null
229
+
230
+ private var lastHeartbeatSentTime: Long = 0
231
+ private var lastHeartbeatReceivedTime: Long = 0
232
+
233
+ private var currentImageChunks: MutableList<ByteArray> = mutableListOf()
234
+
235
+ // Data class to represent a send request
236
+ private data class SendRequest( val data: ByteArray, var waitTime: Int = -1) {
237
+ override fun equals(other: Any?): Boolean {
238
+ if (this === other) return true
239
+ if (javaClass != other?.javaClass) return false
240
+
241
+ other as SendRequest
242
+
243
+ if (!data.contentEquals(other.data)) return false
244
+ if (waitTime != other.waitTime) return false
245
+
246
+ return true
247
+ }
248
+
249
+ override fun hashCode(): Int {
250
+ var result = data.contentHashCode()
251
+ result = 31 * result + waitTime
252
+ return result
253
+ }
254
+ }
255
+
256
+ private class BooleanWaiter {
257
+ @Volatile
258
+ private var flag = true // initially true
259
+
260
+ @Synchronized
261
+ fun waitWhileTrue() {
262
+ while (flag) {
263
+ (this as Object).wait()
264
+ }
265
+ }
266
+
267
+ @Synchronized
268
+ fun setTrue() {
269
+ flag = true
270
+ }
271
+
272
+ @Synchronized
273
+ fun setFalse() {
274
+ flag = false
275
+ (this as Object).notifyAll()
276
+ }
277
+ }
278
+
279
+ // Network Management
280
+ override fun requestWifiScan () { Bridge.log("Nex: requestWifiScan operation not supported") }
281
+ override fun sendWifiCredentials(ssid: String, password: String) { Bridge.log("Nex: sendWifiCredentials operation not supported") }
282
+ override fun forgetWifiNetwork(ssid: String) { }
283
+ override fun sendHotspotState(enabled: Boolean) { Bridge.log("Nex: sendHotspotState operation not supported") }
284
+
285
+ // Gallery: Not supported on Nex (No camera)
286
+ override fun queryGalleryStatus() { Bridge.log("Nex: queryGalleryStatus operation not supported") }
287
+ override fun sendGalleryMode() { Bridge.log("Nex: sendGalleryMode operation not supported") }
288
+
289
+ // Version info: Not supported on Nex (uses protobuf for version info)
290
+ override fun requestVersionInfo() { Bridge.log("Nex: requestVersionInfo operation not supported") }
291
+
292
+ // Camera & Media: Not supported on Nex (No camera)
293
+ override fun requestPhoto(requestId: String, appId: String, size: String, webhookUrl: String?, authToken: String?, compress: String?, flash: Boolean, sound: Boolean) { Bridge.log("Nex: requestPhoto operation not supported") }
294
+ override fun startStream(message: MutableMap<String, Any>) { Bridge.log("Nex: startStream operation not supported") }
295
+ override fun stopStream() { Bridge.log("Nex: stopStream operation not supported") }
296
+ override fun sendStreamKeepAlive(message: MutableMap<String, Any>) { Bridge.log("Nex: sendStreamKeepAlive operation not supported") }
297
+ override fun startVideoRecording(requestId: String, save: Boolean, flash: Boolean, sound: Boolean) { Bridge.log("Nex: startVideoRecording operation not supported") }
298
+ override fun stopVideoRecording(requestId: String) { Bridge.log("Nex: stopVideoRecording operation not supported") }
299
+
300
+ // Button Settings: Not supported on Nex
301
+ override fun sendButtonPhotoSettings() { Bridge.log("Nex: sendButtonPhotoSettings operation not supported") }
302
+ override fun sendButtonVideoRecordingSettings() { Bridge.log("Nex: sendButtonVideoRecordingSettings operation not supported") }
303
+ override fun sendButtonMaxRecordingTime() { Bridge.log("Nex: sendButtonMaxRecordingTime operation not supported") }
304
+ override fun sendButtonCameraLedSetting() { Bridge.log("Nex: sendButtonCameraLedSetting operation not supported") }
305
+
306
+ override fun sendCameraFovSetting() { Bridge.log("Nex: sendCameraFovSetting operation not supported") }
307
+
308
+ override fun sendUserEmailToGlasses(email: String) { Bridge.log("Nex: sendUserEmailToGlasses operation not supported") }
309
+
310
+ override fun sendIncidentId(incidentId: String, apiBaseUrl: String?) {
311
+ Bridge.log("Nex: sendIncidentId operation not supported")
312
+ }
313
+
314
+ // Connection
315
+ override fun findCompatibleDevices() {
316
+ if (isScanningForCompatibleDevices) {
317
+ Bridge.log("Scan already in progress, skipping...")
318
+ return
319
+ }
320
+
321
+ isScanningForCompatibleDevices = true
322
+ val scanner = bluetoothAdapter.bluetoothLeScanner ?: run {
323
+ Log.e(TAG, "BluetoothLeScanner not available")
324
+ isScanningForCompatibleDevices = false
325
+ return
326
+ }
327
+
328
+ val foundDeviceNames = mutableListOf<String>()
329
+ if (findCompatibleDevicesHandler == null) {
330
+ findCompatibleDevicesHandler = Handler(Looper.getMainLooper())
331
+ }
332
+
333
+ // Optional: add filters if you want to narrow the scan
334
+ val filters = mutableListOf<ScanFilter>()
335
+ val settings = ScanSettings.Builder()
336
+ .setScanMode(ScanSettings.SCAN_MODE_BALANCED)
337
+ .build()
338
+
339
+ // Create a modern ScanCallback instead of the deprecated LeScanCallback
340
+ bleScanCallback = object : ScanCallback() {
341
+ override fun onScanResult(callbackType: Int, result: ScanResult) {
342
+ val device = result.device
343
+ val name = device.name
344
+ val address = device.address
345
+
346
+ name?.let {
347
+ if (it.startsWith("Nex1-") || it.startsWith("MENTRA_DISPLAY_")) {
348
+ Bridge.log("bleScanCallback onScanResult: $name address $address")
349
+ synchronized(foundDeviceNames) {
350
+ if (!foundDeviceNames.contains(it)) {
351
+ foundDeviceNames.add(it)
352
+ Bridge.log("Found smart glasses: $name")
353
+ Bridge.sendDiscoveredDevice(DeviceTypes.NEX, it);
354
+ }
355
+ }
356
+ }
357
+ }
358
+ }
359
+
360
+ override fun onBatchScanResults(results: List<ScanResult>) {
361
+ // If needed, handle batch results here
362
+ }
363
+
364
+ override fun onScanFailed(errorCode: Int) {
365
+ Log.e(TAG, "BLE scan failed with code: $errorCode")
366
+ }
367
+ }
368
+
369
+ // Start scanning
370
+ scanner.startScan(filters, settings, bleScanCallback)
371
+ Bridge.log("Started scanning for smart glasses with BluetoothLeScanner...")
372
+ scanner.flushPendingScanResults(bleScanCallback)
373
+
374
+ // Stop scanning after 10 seconds (adjust as needed)
375
+ findCompatibleDevicesHandler?.postDelayed({
376
+ bleScanCallback?.let { scanner.stopScan(it) }
377
+ isScanningForCompatibleDevices = false
378
+ bleScanCallback = null
379
+ Bridge.log("Stopped scanning for smart glasses.")
380
+ }, 10000)
381
+ }
382
+
383
+ override fun connectById(id: String) {
384
+ preferredMainDeviceId = id
385
+ connectToSmartGlasses()
386
+ }
387
+
388
+ override fun getConnectedBluetoothName(): String {
389
+ return mainDevice?.getName() ?: ""
390
+ }
391
+
392
+ override fun disconnect() {
393
+ DeviceStore.apply("glasses", "fullyBooted", false)
394
+ destroy();
395
+ }
396
+
397
+ override fun forget() {
398
+ DeviceStore.apply("glasses", "fullyBooted", false)
399
+ destroy();
400
+ }
401
+
402
+ override fun cleanup() {
403
+ // TODO: Later
404
+ Bridge.log("To be implemented")
405
+ }
406
+
407
+ // Device
408
+ override fun setHeadUpAngle(angle: Int) {
409
+ // Validate headUpAngle range (0 ~ 60)
410
+ val validatedAngle = angle.coerceIn(0, 60)
411
+ Bridge.log("Nex: === SENDING HEAD UP ANGLE COMMAND TO GLASSES ===")
412
+ Bridge.log("Nex: Head Up Angle: $validatedAngle degrees (validated range: 0-60)")
413
+ val cmdBytes = NexProtobufUtils.generateHeadUpAngleConfigCommandBytes(angle)
414
+ sendDataSequentially(cmdBytes, 10)
415
+ }
416
+
417
+ override fun getBatteryStatus() {
418
+ Bridge.log("Nex: Requesting battery status");
419
+ queryBatteryStatus()
420
+ }
421
+
422
+ override fun exit() {
423
+ sendDataSequentially(byteArrayOf(0x18.toByte()), 100)
424
+ }
425
+
426
+ override fun sendShutdown() {
427
+ Bridge.log("sendShutdown - not supported on Nex")
428
+ }
429
+
430
+ override fun sendReboot() {
431
+ Bridge.log("sendReboot - not supported on Nex")
432
+ }
433
+
434
+ override fun sendRgbLedControl(requestId: String, packageName: String?, action: String, color: String?, ontime: Int, offtime: Int, count: Int) {
435
+ Bridge.log("sendRgbLedControl - not supported on Nex");
436
+ Bridge.sendRgbLedControlResponse(requestId, false, "device_not_supported");
437
+ }
438
+
439
+ // Not supported in Nex
440
+ override fun setSilentMode(enabled: Boolean) { Bridge.log("Nex: setSilentMode operation not supported") }
441
+
442
+ // Display
443
+ override fun setBrightness(level: Int, autoMode: Boolean) {
444
+ // TODO: test this logic. Is it correct? Should we send both or just one?
445
+ Bridge.log("Nex: setBrightness() - level: " + level + "%, autoMode: " + autoMode);
446
+ val brightnessCmdBytes = NexProtobufUtils.generateBrightnessConfigCommandBytes(level)
447
+ sendDataSequentially(brightnessCmdBytes, 10)
448
+
449
+ val autoBrightnessCmdBytes = NexProtobufUtils.generateAutoBrightnessConfigCommandBytes(autoMode)
450
+ sendDataSequentially(autoBrightnessCmdBytes, 10)
451
+ }
452
+
453
+ override fun clearDisplay() {
454
+ Bridge.log("Nex: clearDisplay() - sending clear display request command bytes");
455
+ val clearDisplayPackets = NexProtobufUtils.generateClearDisplayRequestCommandBytes()
456
+ sendDataSequentially(clearDisplayPackets, 10)
457
+ // sendTextWall(" ")
458
+ Bridge.log("Nex: clearDisplay() - sent clear display request command bytes");
459
+ }
460
+
461
+ override fun sendTextWall(text: String) {
462
+ Bridge.log("Nex: sendTextWall() - text: " + text);
463
+ val textChunks: ByteArray = createTextWallChunksForNex(text)
464
+ sendDataSequentially(textChunks);
465
+ }
466
+
467
+ override fun sendDoubleTextWall(top: String, bottom: String) {
468
+ Bridge.log("Nex: sendDoubleTextWall() - top: " + top + ", bottom: " + bottom);
469
+ val finalText: String = buildString {
470
+ top?.let { append(it) }
471
+ top?.let { append("\n") }
472
+ bottom?.let { append(it) }
473
+ }
474
+ val textChunks = createTextWallChunksForNex(finalText)
475
+ sendDataSequentially(textChunks)
476
+ }
477
+
478
+ override fun displayBitmap(base64ImageData: String): Boolean {
479
+ try {
480
+ val bmpData: ByteArray? = android.util.Base64.decode(base64ImageData, android.util.Base64.DEFAULT)
481
+ if (bmpData == null || bmpData.isEmpty()) {
482
+ Log.e(TAG, "Failed to decode base64 image data");
483
+ return false;
484
+ }
485
+ val bmp = BitmapFactory.decodeByteArray(bmpData, 0, bmpData.size)
486
+
487
+ displayBitmapImageForNexGlasses(bmpData, bmp.width, bmp.height)
488
+ return true
489
+
490
+ } catch (e: Exception) {
491
+ Log.e(TAG, "Error in displaying bitmap: " + e.message);
492
+ return false
493
+ }
494
+
495
+ }
496
+ override fun showDashboard() {
497
+ exit()
498
+ }
499
+
500
+ override fun setDashboardPosition(height: Int, depth: Int) {
501
+ Bridge.log("Nex: setDashboardPosition() - height: " + height + ", depth: " + depth)
502
+ // Send display_height then display_distance; adjust order if Nex firmware requires otherwise.
503
+ val heightBytes = NexProtobufUtils.generateDisplayHeightCommandBytes(height)
504
+ sendDataSequentially(heightBytes, 10)
505
+ val distanceCm = NexProtobufUtils.dashboardDepthToDistanceCm(depth)
506
+ val distanceBytes = NexProtobufUtils.generateDisplayDistanceCommandBytes(distanceCm)
507
+ sendDataSequentially(distanceBytes, 10)
508
+ }
509
+
510
+ override fun setDashboardHeightOnly(height: Int) {
511
+ Bridge.log("Nex: setDashboardHeightOnly() - height: $height")
512
+ val heightBytes = NexProtobufUtils.generateDisplayHeightCommandBytes(height)
513
+ sendDataSequentially(heightBytes, 10)
514
+ }
515
+
516
+ override fun setDashboardDepthOnly(depth: Int) {
517
+ Bridge.log("Nex: setDashboardDepthOnly() - depth: $depth")
518
+ val distanceCm = NexProtobufUtils.dashboardDepthToDistanceCm(depth)
519
+ val distanceBytes = NexProtobufUtils.generateDisplayDistanceCommandBytes(distanceCm)
520
+ sendDataSequentially(distanceBytes, 10)
521
+ }
522
+
523
+ override fun ping() {
524
+ Bridge.log("Nex: ping()");
525
+ }
526
+
527
+ override fun dbg1() {}
528
+ override fun dbg2() {}
529
+
530
+ // Audio Control
531
+ // TODO: Validate this logic. looks weird.
532
+ override fun setMicEnabled(enabled: Boolean) {
533
+ Bridge.log("Nex: setMicEnabled(): $enabled")
534
+ // Update the shouldUseGlassesMic flag to reflect the current state
535
+ shouldUseGlassesMic = enabled
536
+ Bridge.log("Nex: Updated shouldUseGlassesMic to: $shouldUseGlassesMic")
537
+ if (enabled) {
538
+ Bridge.log("Nex: Microphone enabled, starting audio input handling")
539
+ startMicBeat(MICBEAT_INTERVAL_MS.toInt())
540
+ } else {
541
+ Bridge.log("Nex: Microphone disabled, stopping audio input handling")
542
+ stopMicBeat()
543
+ }
544
+ }
545
+
546
+ override fun sortMicRanking(list: MutableList<String>): MutableList<String> {
547
+ return list
548
+ }
549
+
550
+ //==================== HELPER FUNCTIONS =================================================
551
+ private fun createGattCallback(): BluetoothGattCallback {
552
+ return object : BluetoothGattCallback() {
553
+ override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
554
+ Bridge.log("onConnectionStateChange status State ${status == BluetoothGatt.GATT_SUCCESS}")
555
+ Bridge.log("onConnectionStateChange connected State ${newState == BluetoothProfile.STATE_CONNECTED}")
556
+
557
+ if (status == BluetoothGatt.GATT_SUCCESS) {
558
+ if (newState == BluetoothProfile.STATE_CONNECTED) {
559
+ Bridge.log("glass connected, discovering services...")
560
+ gatt.requestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_HIGH)
561
+ gatt.setPreferredPhy(
562
+ BluetoothDevice.PHY_LE_2M_MASK,
563
+ BluetoothDevice.PHY_LE_2M_MASK,
564
+ BluetoothDevice.PHY_OPTION_NO_PREFERRED
565
+ )
566
+
567
+ isMainConnected = true
568
+ mainReconnectAttempts = 0
569
+ Bridge.log("Both glasses connected. Stopping BLE scan.")
570
+ stopScan()
571
+
572
+ if (!isWorkerRunning) {
573
+ Bridge.log("Worker thread is not running. Starting it.")
574
+ startWorkerIfNeeded()
575
+ }
576
+
577
+ Bridge.log("Discover services calling...")
578
+ gatt.discoverServices()
579
+ updateConnectionState()
580
+ mainDevice?.let { device ->
581
+ savedNexMainName = device.name
582
+ savedNexMainAddress = device.address
583
+ // savePairedDeviceNames()
584
+ // savePairedDeviceAddress()
585
+ }
586
+ } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
587
+ handleDisconnection(gatt)
588
+ }
589
+ } else {
590
+ handleConnectionFailure(gatt, status)
591
+ }
592
+ }
593
+
594
+ private fun handleDisconnection(gatt: BluetoothGatt) {
595
+ Bridge.log("glass disconnected, stopping heartbeats")
596
+ Bridge.log("Entering STATE_DISCONNECTED branch")
597
+
598
+ // Save current microphone state before disconnection
599
+ microphoneStateBeforeDisconnection = isMicrophoneEnabled
600
+ Bridge.log("Saved microphone state before disconnection: $microphoneStateBeforeDisconnection")
601
+
602
+ // Reset protobuf version posted flag for next connection
603
+ protobufVersionPosted = false
604
+
605
+ // Reset chunk sizes to defaults
606
+ maxChunkSize = MAX_CHUNK_SIZE_DEFAULT
607
+ bmpChunkSize = MAX_CHUNK_SIZE_DEFAULT
608
+ mainServicesWaiter.setTrue()
609
+ mainWaiter.setFalse()
610
+ Bridge.log("[BLE] Disconnect: reset mainServicesWaiter=true, mainWaiter=false (unblock any in-flight write). isWorkerRunning=$isWorkerRunning")
611
+ forceSideDisconnection()
612
+ Bridge.log("Called forceSideDisconnection()")
613
+ currentMTU = 0
614
+
615
+ // Stop any periodic transmissions
616
+ stopMicBeat()
617
+ sendQueue.clear()
618
+ Bridge.log("Stopped heartbeat monitoring and mic beat; cleared sendQueue")
619
+ updateConnectionState()
620
+ Bridge.log("Updated connection state after disconnection")
621
+
622
+ // gatt.device?.let {
623
+ // Bridge.log("Closing GATT connection for device: ${it.address}")
624
+ // gatt.disconnect()
625
+ // gatt.close()
626
+ // Bridge.log("GATT connection closed")
627
+ // } ?: run {
628
+ // Bridge.log("No GATT device available to disconnect")
629
+ // }
630
+
631
+ mainTaskHandler?.sendEmptyMessageDelayed(MAIN_TASK_HANDLER_CODE_RECONNECT_DEVICE, 2000)
632
+ }
633
+
634
+ private fun handleConnectionFailure(gatt: BluetoothGatt, status: Int) {
635
+ // Save current microphone state before connection failure
636
+ microphoneStateBeforeDisconnection = isMicrophoneEnabled
637
+ Bridge.log("Saved microphone state before connection failure: $microphoneStateBeforeDisconnection")
638
+
639
+ currentMTU = 0
640
+ maxChunkSize = MAX_CHUNK_SIZE_DEFAULT
641
+ bmpChunkSize = MAX_CHUNK_SIZE_DEFAULT
642
+ Bridge.log("Unexpected connection state encountered for glass: $status")
643
+ stopMicBeat()
644
+ sendQueue.clear()
645
+
646
+ // Mark as not ready
647
+ mainServicesWaiter.setTrue()
648
+ mainWaiter.setFalse()
649
+ Bridge.log("[BLE] ConnectionFailure: reset mainServicesWaiter=true, mainWaiter=false (unblock any in-flight write). isWorkerRunning=$isWorkerRunning")
650
+ Bridge.log("Stopped heartbeat monitoring and mic beat; cleared sendQueue due to connection failure")
651
+ Bridge.log("glass connection failed with status: $status")
652
+
653
+ isMainConnected = false
654
+ mainReconnectAttempts++
655
+
656
+ mainGlassGatt?.let {
657
+ it.disconnect()
658
+ it.close()
659
+ mainGlassGatt = null
660
+ }
661
+
662
+ forceSideDisconnection()
663
+ Bridge.log("Called forceSideDisconnection() after connection failure")
664
+ Bridge.log("GATT connection disconnected and closed due to failure")
665
+ updateConnectionState()
666
+
667
+ mainTaskHandler?.sendEmptyMessageDelayed(MAIN_TASK_HANDLER_CODE_RECONNECT_DEVICE, 2000)
668
+ }
669
+
670
+ private fun forceSideDisconnection() {
671
+ Bridge.log("forceSideDisconnection() called")
672
+ isMainConnected = false
673
+ mainReconnectAttempts++
674
+ Bridge.log("Main glass: Marked as disconnected and incremented mainReconnectAttempts to $mainReconnectAttempts")
675
+
676
+ mainGlassGatt?.let {
677
+ Bridge.log("Main glass GATT exists. Disconnecting and closing mainGlassGatt")
678
+ it.disconnect()
679
+ it.close()
680
+ mainGlassGatt = null
681
+ } ?: run {
682
+ Bridge.log("Main glass GATT is already null")
683
+ }
684
+ }
685
+
686
+ override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
687
+ val statusBool = status == BluetoothGatt.GATT_SUCCESS
688
+ mainTaskHandler?.obtainMessage(
689
+ MAIN_TASK_HANDLER_CODE_DISCOVER_SERVICES,
690
+ statusBool
691
+ )?.sendToTarget()
692
+ }
693
+
694
+ override fun onCharacteristicWrite(
695
+ gatt: BluetoothGatt,
696
+ characteristic: BluetoothGattCharacteristic,
697
+ status: Int
698
+ ) {
699
+ Bridge.log("onCharacteristicWrite callback - ")
700
+ val values = characteristic.value ?: byteArrayOf()
701
+
702
+ if (status == BluetoothGatt.GATT_SUCCESS) {
703
+ Bridge.log("[BLE] onCharacteristicWrite: SUCCESS len=${values.size}")
704
+ val packetHex = values.joinToString("") { "%02X".format(it) }
705
+ Bridge.log("onCharacteristicWrite Values - $packetHex")
706
+
707
+ if (values.isNotEmpty()) {
708
+ val packetType = values[0]
709
+ val protobufData = values.copyOfRange(1, values.size)
710
+
711
+ if (packetType == NexBluetoothPacketTypes.PACKET_TYPE_PROTOBUF) {
712
+ // just for test
713
+ decodeProtobufsByWrite(protobufData, packetHex)
714
+ }
715
+ }
716
+ } else {
717
+ Log.e(TAG, "[BLE] onCharacteristicWrite: FAILED status=$status${if (status == 133) " (GATT_ERROR 133 - likely disconnected mid-write)" else ""}")
718
+ }
719
+
720
+ // clear the waiter
721
+ Bridge.log("[BLE] onCharacteristicWrite: releasing mainWaiter")
722
+ mainWaiter.setFalse()
723
+ }
724
+
725
+ override fun onDescriptorWrite(gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int) {
726
+ val statusStr = if (status == BluetoothGatt.GATT_SUCCESS) "SUCCESS" else "FAILED($status)"
727
+ Bridge.log("[BLE] onDescriptorWrite: $statusStr — releasing mainServicesWaiter, worker can now send")
728
+ mainServicesWaiter.setFalse()
729
+ }
730
+
731
+ override fun onMtuChanged(gatt: BluetoothGatt, mtu: Int, status: Int) {
732
+ val statusBool = status == BluetoothGatt.GATT_SUCCESS
733
+ Bridge.log("🔄 MTU Negotiation Result: Success=$statusBool, Device MTU=$mtu, Status=$status")
734
+
735
+ if (statusBool) {
736
+ // Store device capability and calculate actual negotiated MTU
737
+ deviceMaxMTU = mtu // Record what device actually supports
738
+ // The actual negotiated MTU is the minimum of our request and device capability
739
+ currentMTU = MTU_517.coerceAtMost(mtu)
740
+
741
+ Bridge.log("🎯 MTU Negotiation Complete:")
742
+ Bridge.log(" 📱 App Requested: $MTU_517 bytes")
743
+ Bridge.log(" 📡 Device Supports: $deviceMaxMTU bytes")
744
+ Bridge.log(" 🤝 Negotiated MTU: $currentMTU bytes")
745
+
746
+ // Calculate optimal chunk sizes based on negotiated MTU
747
+ maxChunkSize = currentMTU - 10
748
+ bmpChunkSize = currentMTU - 20 // BMP has more config bytes
749
+
750
+ Bridge.log("✅ MTU Configuration Complete:")
751
+ Bridge.log(" 📊 Final MTU: $currentMTU bytes")
752
+ Bridge.log(" 📦 Data Chunk Size: $maxChunkSize bytes")
753
+ Bridge.log(" 🖼️ Image Chunk Size: $bmpChunkSize bytes")
754
+ Bridge.log(" 🔧 Device Maximum: $deviceMaxMTU bytes")
755
+ } else {
756
+ Bridge.log("❌ MTU Request Failed - Status: $status, Requested: $mtu")
757
+
758
+ // Simple fallback strategy: 247 → 23
759
+ if (mtu == MTU_517) {
760
+ Bridge.log("🔄 247 bytes failed, trying default: $MTU_DEFAULT bytes...")
761
+ Bridge.log("📤 Requesting MTU: $MTU_DEFAULT bytes (fallback)")
762
+ gatt.requestMtu(MTU_DEFAULT)
763
+ } else {
764
+ Bridge.log("⚠️ All MTU requests failed, using defaults")
765
+ currentMTU = MTU_DEFAULT
766
+ deviceMaxMTU = MTU_DEFAULT
767
+ maxChunkSize = MAX_CHUNK_SIZE_DEFAULT
768
+ bmpChunkSize = MAX_CHUNK_SIZE_DEFAULT
769
+
770
+ Bridge.log("📋 Fallback Configuration:")
771
+ Bridge.log(" 📊 Default MTU: $MTU_DEFAULT bytes")
772
+ Bridge.log(" 📦 Data Chunk Size: $maxChunkSize bytes")
773
+ Bridge.log(" 🖼️ Image Chunk Size: $bmpChunkSize bytes")
774
+ }
775
+ }
776
+ }
777
+
778
+ override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {
779
+ val msg = Message.obtain().apply {
780
+ what = MAIN_TASK_HANDLER_CODE_CHARACTERISTIC_VALUE_NOTIFIED
781
+ obj = characteristic // Attach any object you want
782
+ }
783
+ mainTaskHandler.sendMessage(msg)
784
+ }
785
+ }
786
+ }
787
+
788
+ private fun handleMainTaskMessage(msg: Message): Boolean {
789
+ val msgCode = msg.what
790
+ Bridge.log("Nex: handleMessage msgCode: $msgCode")
791
+ Bridge.log("Nex: handleMessage obj: ${msg.obj}")
792
+
793
+ when (msgCode) {
794
+ MAIN_TASK_HANDLER_CODE_GATT_STATUS_CHANGED -> {}
795
+ MAIN_TASK_HANDLER_CODE_DISCOVER_SERVICES -> {
796
+ val statusBool = msg.obj as? Boolean ?: false
797
+ if (statusBool) {
798
+ mainGlassGatt?.let { initNexGlasses(it) }
799
+ }
800
+ }
801
+ MAIN_TASK_HANDLER_CODE_CHARACTERISTIC_VALUE_NOTIFIED -> {
802
+ val characteristic = msg.obj as? BluetoothGattCharacteristic
803
+ characteristic?.let { onCharacteristicChangedHandler(it) }
804
+ }
805
+ MAIN_TASK_HANDLER_CODE_CONNECT_DEVICE -> {}
806
+ MAIN_TASK_HANDLER_CODE_DISCONNECT_DEVICE -> {}
807
+ MAIN_TASK_HANDLER_CODE_RECONNECT_DEVICE -> {
808
+ mainDevice?.let { attemptGattConnection(it) }
809
+ }
810
+ MAIN_TASK_HANDLER_CODE_CANCEL_RECONNECT_DEVICE -> {}
811
+ MAIN_TASK_HANDLER_CODE_RECONNECT_GATT -> {}
812
+ MAIN_TASK_HANDLER_CODE_SCAN_START -> {}
813
+ MAIN_TASK_HANDLER_CODE_SCAN_END -> {}
814
+ MAIN_TASK_HANDLER_CODE_BATTERY_QUERY -> {
815
+ queryBatteryStatus()
816
+ }
817
+ MAIN_TASK_HANDLER_CODE_HEART_BEAT -> {
818
+ // Note: Heartbeat is now handled by receiving ping from glasses
819
+ // This case is kept for backward compatibility but no longer used
820
+ Bridge.log("Nex: Heartbeat handler called - no longer sending periodic pings")
821
+ }
822
+ else -> { }
823
+ }
824
+
825
+ return true
826
+ }
827
+
828
+ private fun initNexGlasses(gatt: BluetoothGatt) {
829
+ // Start MTU discovery with our maximum target
830
+ Bridge.log("Nex: 🔍 MTU Discovery: Requesting maximum MTU size: $MTU_517")
831
+ Bridge.log("Nex: 🎯 Target: Use $MTU_517 bytes max, or $MTU_DEFAULT bytes default")
832
+ Bridge.log("Nex: 📤 Requesting MTU: $MTU_517 bytes")
833
+ gatt.requestMtu(MTU_517) // Request our maximum MTU
834
+
835
+ val uartService: BluetoothGattService = gatt.getService(NexBluetoothConstants.MAIN_SERVICE_UUID)
836
+
837
+ if (uartService != null) {
838
+ val writeChar = uartService.getCharacteristic(NexBluetoothConstants.WRITE_CHAR_UUID)
839
+ val notifyChar = uartService.getCharacteristic(NexBluetoothConstants.NOTIFY_CHAR_UUID)
840
+
841
+ writeChar?.let {
842
+ mainWriteChar = it
843
+ Bridge.log("Nex: glass TX characteristic found")
844
+ }
845
+
846
+ notifyChar?.let {
847
+ mainNotifyChar = it
848
+ enableNotification(gatt, it)
849
+ Bridge.log("Nex: glass RX characteristic found")
850
+ }
851
+
852
+ // Mark as connected but wait for setup below to update connection state
853
+ isMainConnected = true
854
+ Bridge.log("Nex: PROC_QUEUE - left side setup complete")
855
+
856
+ if (isMainConnected) {
857
+ // Do first battery status query
858
+ mainTaskHandler.sendEmptyMessageDelayed(MAIN_TASK_HANDLER_CODE_BATTERY_QUERY, 10)
859
+
860
+ // Restore previous microphone state or disable if this is the first connection
861
+ val shouldRestoreMic = microphoneStateBeforeDisconnection
862
+ Bridge.log("Nex: `Restoring microphone state to: $shouldRestoreMic (previous state: $microphoneStateBeforeDisconnection)")
863
+
864
+ if (shouldRestoreMic) {
865
+ startMicBeat(MICBEAT_INTERVAL_MS.toInt())
866
+ }
867
+
868
+ // Enable our AugmentOS notification key
869
+ sendWhiteListCommand(10)
870
+
871
+ showHomeScreen() // Turn on the NexGlasses display
872
+ updateConnectionState()
873
+
874
+ if (hasConnectedThisSession) {
875
+ Bridge.log("Nex: BLE reconnect detected — showing reconnected banner")
876
+ sendTextWall("// MentraOS Reconnected")
877
+ mainTaskHandler.postDelayed({ clearDisplay() }, 3000)
878
+ } else {
879
+ hasConnectedThisSession = true
880
+ }
881
+
882
+ // Post protobuf schema version information (only once)
883
+ if (!protobufVersionPosted) {
884
+ postProtobufSchemaVersionInfo()
885
+ protobufVersionPosted = true
886
+ }
887
+
888
+ // Query glasses protobuf version from firmware
889
+ Bridge.log("=== SENDING GLASSES PROTOBUF VERSION REQUEST ===")
890
+ val versionQueryPacket = NexProtobufUtils.generateVersionRequestCommandBytes()
891
+ sendDataSequentially(versionQueryPacket, 100)
892
+ Bridge.log("Sent glasses protobuf version request")
893
+ }
894
+ } else {
895
+ Log.e(TAG, " glass UART service not found")
896
+ }
897
+ }
898
+
899
+ private fun enableNotification(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {
900
+ Bridge.log("Nex: PROC_QUEUE - Starting notification setup")
901
+
902
+ // Enable notifications
903
+ val result = gatt.setCharacteristicNotification(characteristic, true)
904
+ Bridge.log("Nex: PROC_QUEUE - setCharacteristicNotification result: $result")
905
+
906
+ // Set write type for the characteristic
907
+ characteristic.writeType = BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT
908
+ Bridge.log("Nex: PROC_QUEUE - write type set")
909
+
910
+ // Add delay
911
+ Bridge.log("Nex: PROC_QUEUE - waiting to enable notification...")
912
+ try {
913
+ Thread.sleep(100)
914
+ } catch (e: InterruptedException) {
915
+ Log.e(TAG, "Error sending data: ${e.message}")
916
+ }
917
+
918
+ // Get and configure descriptor
919
+ val descriptor = characteristic.getDescriptor(NexBluetoothConstants.CLIENT_CHARACTERISTIC_CONFIG_UUID)
920
+ descriptor?.let {
921
+ it.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
922
+ val writeResult = gatt.writeDescriptor(it)
923
+ Bridge.log("Nex: PROC_QUEUE - set descriptor with result: $writeResult")
924
+ }
925
+ }
926
+
927
+ private fun onCharacteristicChangedHandler(characteristic: BluetoothGattCharacteristic) {
928
+ if (characteristic.uuid == NexBluetoothConstants.NOTIFY_CHAR_UUID) {
929
+ val data = characteristic.value
930
+ val deviceName = mainGlassGatt?.device?.name ?: return
931
+ val packetHex = data.joinToString("") { "%02X".format(it) }
932
+ Bridge.log("onCharacteristicChangedHandler len: ${data.size}")
933
+ Bridge.log("onCharacteristicChangedHandler: $packetHex")
934
+
935
+ if (data.isEmpty()) return
936
+
937
+ val packetType = data[0]
938
+ Bridge.log("onCharacteristicChangedHandler packetType: ${String.format("%02X ", packetType)}")
939
+
940
+ when (packetType) {
941
+ NexBluetoothPacketTypes.PACKET_TYPE_JSON -> {
942
+ val jsonData = data.copyOfRange(1, data.size)
943
+ decodeJsons(jsonData)
944
+ }
945
+ NexBluetoothPacketTypes.PACKET_TYPE_PROTOBUF -> {
946
+ val protobufData = data.copyOfRange(1, data.size)
947
+ decodeProtobufs(protobufData, packetHex)
948
+ }
949
+ NexBluetoothPacketTypes.PACKET_TYPE_AUDIO -> {
950
+ // Check for audio packet header
951
+ if (data[0] == 0xA0.toByte()) {
952
+ val sequenceNumber = data[1]
953
+ val receiveTime = System.currentTimeMillis()
954
+
955
+ // Basic sequence validation
956
+ if (lastReceivedLc3Sequence != -1 && (lastReceivedLc3Sequence + 1).toByte() != sequenceNumber) {
957
+ Bridge.log("LC3 packet sequence mismatch. Expected: ${lastReceivedLc3Sequence + 1}, Got: $sequenceNumber")
958
+ }
959
+ lastReceivedLc3Sequence = sequenceNumber.toInt()
960
+
961
+ val lc3Data = data.copyOfRange(2, data.size)
962
+
963
+ Bridge.log("Received LC3 audio packet seq=$sequenceNumber, size=${lc3Data.size}")
964
+
965
+ // Play LC3 audio directly through LC3 player
966
+ if (lc3AudioPlayer != null && isLc3AudioEnabled) {
967
+ // Use the original packet format that LC3 player expects
968
+ lc3AudioPlayer?.write(data, 0, data.size)
969
+ Bridge.log("Playing LC3 audio directly through LC3 player: ${data.size} bytes")
970
+ } else if (!isLc3AudioEnabled) {
971
+ Bridge.log("LC3 audio disabled - skipping LC3 audio output")
972
+ } else {
973
+ Bridge.log("LC3 player not available - skipping LC3 audio output")
974
+ }
975
+
976
+ DeviceManager.getInstance().handleGlassesMicData(lc3Data);
977
+
978
+ // Still decode for callback compatibility
979
+ // TODO: (Verify) Commenting because commented in G1
980
+ // if (lc3DecoderPtr != 0L) {
981
+ // val pcmData = Lc3Cpp.decodeLC3(lc3DecoderPtr, lc3Data)
982
+ // Bridge.log("pcmData size:${pcmData?.size ?: 0}")
983
+ // audioProcessingCallback?.let { callback ->
984
+ // if (!pcmData.isNullOrEmpty()) {
985
+ // callback.onAudioDataAvailable(pcmData)
986
+ // }
987
+ // } ?: run {
988
+ // // If we get here, it means the callback wasn't properly registered
989
+ // Log.e(TAG,"Audio processing callback is null - callback registration failed!")
990
+ // }
991
+ // }
992
+ }
993
+ }
994
+ NexBluetoothPacketTypes.PACKET_TYPE_IMAGE -> {
995
+ // Handle image packet if needed
996
+ }
997
+ else -> {
998
+ Bridge.log("unknown packetType: ${String.format("%02X ", packetType)}")
999
+ }
1000
+ }
1001
+ }
1002
+ }
1003
+
1004
+ private fun attemptGattConnection(device: BluetoothDevice) {
1005
+ if (device == null) {
1006
+ Bridge.log("Cannot connect to GATT: Device is null")
1007
+ return
1008
+ }
1009
+
1010
+ val deviceName = device.name
1011
+ if (deviceName.isNullOrEmpty()) {
1012
+ Log.e(TAG,"Skipping null/empty device name: ${device.address}... this means something horrific has occurred. Look into this.")
1013
+ return
1014
+ }
1015
+
1016
+ Bridge.log("attemptGattConnection called for device: $deviceName (${device.address})")
1017
+
1018
+ DeviceStore.apply("glasses", "connectionState", ConnTypes.CONNECTING)
1019
+ Bridge.log("Setting connectionState to CONNECTING. Notifying connectionEvent.")
1020
+ // connectionEvent(connectionState)
1021
+
1022
+ if (mainGlassGatt == null) {
1023
+ Bridge.log("Attempting GATT connection for Main Glass...")
1024
+ mainGlassGatt = device.connectGatt(context, false, mainGattCallback)
1025
+ isMainConnected = false
1026
+ Bridge.log("Main GATT connection initiated. isMainConnected set to false.")
1027
+ } else {
1028
+ Bridge.log("Main Glass GATT already exists")
1029
+ }
1030
+ }
1031
+
1032
+ private fun postProtobufSchemaVersionInfo() {
1033
+ try {
1034
+ // Call the version method only once
1035
+ val schemaVersion = NexProtobufUtils.getProtobufSchemaVersion(context!!)
1036
+
1037
+ // Build the info string directly instead of calling getProtobufBuildInfo()
1038
+ val fileDescriptorName = mentraos.ble.MentraosBle.getDescriptor().file.name
1039
+ val buildInfo = "Schema v$schemaVersion | $fileDescriptorName"
1040
+
1041
+ // protobufSchemaVersion = schemaVersion.toString()
1042
+
1043
+ // val event = ProtobufSchemaVersionEvent(
1044
+ // schemaVersion,
1045
+ // buildInfo,
1046
+ // smartGlassesDevice?.deviceModelName ?: "Unknown"
1047
+ // )
1048
+
1049
+ // EventBus.getDefault().post(event)
1050
+ Bridge.log("Posted protobuf schema version event: $buildInfo")
1051
+ } catch (e: Exception) {
1052
+ Log.e(TAG,"Error posting protobuf schema version event: ${e.message}", e)
1053
+ }
1054
+ }
1055
+
1056
+ private fun connectToSmartGlasses() {
1057
+ val deviceModelName = DeviceManager.getInstance().deviceName
1058
+ val deviceAddress = DeviceManager.getInstance().deviceAddress
1059
+
1060
+ // Register bonding receiver
1061
+ Bridge.log("connectToSmartGlasses start")
1062
+ Bridge.log("try to ConnectToSmartGlassesing deviceModelName: ${deviceModelName} deviceAddress: ${deviceAddress}")
1063
+ preferredMainDeviceId = DeviceManager.getInstance().deviceName
1064
+ if (!bluetoothAdapter.isEnabled) {
1065
+ return
1066
+ }
1067
+ when {
1068
+ !deviceModelName.isNullOrEmpty() && !deviceAddress.isNullOrEmpty() -> {
1069
+ stopScan()
1070
+ mainDevice = bluetoothAdapter.getRemoteDevice(deviceAddress)
1071
+ mainTaskHandler?.sendEmptyMessageDelayed(MAIN_TASK_HANDLER_CODE_RECONNECT_DEVICE, 0)
1072
+ }
1073
+ savedNexMainAddress != null -> {
1074
+ mainDevice = bluetoothAdapter.getRemoteDevice(savedNexMainAddress)
1075
+ mainTaskHandler?.sendEmptyMessageDelayed(MAIN_TASK_HANDLER_CODE_RECONNECT_DEVICE, 0)
1076
+ }
1077
+ else -> {
1078
+ // Start scanning for devices
1079
+ stopScan()
1080
+ DeviceStore.apply("glasses", "connectionState", ConnTypes.SCANNING)
1081
+ // connectionEvent(connectionState) // TODO: Figure out where is connection event defined????
1082
+ startScan()
1083
+ }
1084
+ }
1085
+ }
1086
+
1087
+ private fun destroy() {
1088
+ Bridge.log("Nex: MentraNexSGC ONDESTROY")
1089
+ showHomeScreen()
1090
+ isKilled = true
1091
+ // stop BLE scanning
1092
+ stopScan()
1093
+ // Save current microphone state before destroying
1094
+ microphoneStateBeforeDisconnection = isMicrophoneEnabled
1095
+ Bridge.log("Nex: Saved microphone state during destroy: $microphoneStateBeforeDisconnection")
1096
+ // disable the microphone and stop sending micbeat
1097
+ stopMicBeat()
1098
+ // Stop periodic notifications
1099
+ stopPeriodicNotifications()
1100
+ mainGlassGatt?.let { gatt ->
1101
+ gatt.disconnect()
1102
+ gatt.close()
1103
+ mainGlassGatt = null
1104
+ }
1105
+ lc3AudioPlayer?.let { player ->
1106
+ try {
1107
+ player.stopPlay()
1108
+ Bridge.log("Nex: LC3 audio player stopped and cleaned up")
1109
+ } catch (e: Exception) {
1110
+ Log.e(TAG,"Nex: Error stopping LC3 audio player during destroy: ${e.message}")
1111
+ } finally {
1112
+ lc3AudioPlayer = null
1113
+ }
1114
+ }
1115
+ // Clean up handlers
1116
+ mainTaskHandler?.removeCallbacksAndMessages(null)
1117
+ whiteListHandler?.removeCallbacksAndMessages(null)
1118
+ micEnableHandler?.removeCallbacksAndMessages(null)
1119
+ notificationRunnable?.let {
1120
+ notificationHandler?.removeCallbacks(it)
1121
+ }
1122
+
1123
+ textWallRunnable?.let {
1124
+ textWallHandler?.removeCallbacks(it)
1125
+ }
1126
+ findCompatibleDevicesHandler?.removeCallbacksAndMessages(null)
1127
+ // Free LC3 decoder
1128
+ if (lc3DecoderPtr != 0L) {
1129
+ Lc3Cpp.freeDecoder(lc3DecoderPtr)
1130
+ lc3DecoderPtr = 0L
1131
+ }
1132
+ currentImageChunks.clear()
1133
+ isImageSendProgressing = false
1134
+ sendQueue.clear()
1135
+ // Add a dummy element to unblock the take() call if needed
1136
+ sendQueue.offer(emptyArray()) // is this needed?
1137
+ isWorkerRunning = false
1138
+ isMainConnected = false
1139
+ hasConnectedThisSession = false
1140
+ Bridge.log("Nex: MentraNexSGC cleanup complete")
1141
+ }
1142
+
1143
+ private fun startScan() {
1144
+ val scanner: BluetoothLeScanner = bluetoothAdapter.bluetoothLeScanner
1145
+ if (scanner == null) {
1146
+ Log.e(TAG,"BluetoothLeScanner not available.")
1147
+ return
1148
+ }
1149
+
1150
+ // Optionally, define filters if needed
1151
+ val filters = mutableListOf<ScanFilter>()
1152
+ // For example, to filter by device name:
1153
+ // filters.add(ScanFilter.Builder().setDeviceName("Even G1_").build())
1154
+
1155
+ // Set desired scan settings
1156
+ val settings = ScanSettings.Builder()
1157
+ .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
1158
+ .build()
1159
+
1160
+ // Start scanning
1161
+ isScanning = true
1162
+ scanner.startScan(filters, settings, modernScanCallback)
1163
+ scanner.flushPendingScanResults(modernScanCallback)
1164
+ Bridge.log("CALL START SCAN - Started scanning for devices...")
1165
+
1166
+ // Ensure scanning state is immediately communicated to UI
1167
+ DeviceStore.apply("glasses", "connectionState", ConnTypes.SCANNING)
1168
+ // connectionEvent(connectionState)
1169
+
1170
+ // Stop the scan after some time (e.g., 10-15s instead of 60 to avoid
1171
+ // throttling)
1172
+ // handler.postDelayed({ stopScan() }, 10000)
1173
+ }
1174
+
1175
+ override fun stopScan() {
1176
+ val scanner = bluetoothAdapter.bluetoothLeScanner
1177
+ scanner?.stopScan(modernScanCallback)
1178
+ isScanning = false
1179
+ Bridge.log("Stopped scanning for devices")
1180
+
1181
+ if (bleScanCallback != null && isScanningForCompatibleDevices) {
1182
+ scanner?.stopScan(bleScanCallback)
1183
+ isScanningForCompatibleDevices = false
1184
+ }
1185
+ }
1186
+
1187
+ private fun displayBitmapImageForNexGlasses(bmpData: ByteArray, width: Int, height: Int) {
1188
+ Bridge.log("Starting BMP display process for ${width}x$height image")
1189
+
1190
+ try {
1191
+ if (bmpData.isEmpty()) {
1192
+ Log.e(TAG,"Invalid BMP data provided")
1193
+ return
1194
+ }
1195
+
1196
+ Bridge.log("Processing BMP data, size: ${bmpData.size} bytes")
1197
+
1198
+ // Generate proper 2-byte hex stream ID (e.g., "002A") as per protobuf specification
1199
+ val totalChunks = (bmpData.size + bmpChunkSize - 1) / bmpChunkSize
1200
+ val streamId = "%04X".format(random.nextInt(0x10000)) // 4-digit hex format
1201
+
1202
+ val startImageSendingBytes = NexProtobufUtils.generateDisplayImageCommandBytes(streamId, totalChunks, width, height)
1203
+ sendDataSequentially(startImageSendingBytes)
1204
+
1205
+ // Send all chunks with proper stream ID parsing
1206
+ val chunks = NexProtobufUtils.createBmpChunksForNexGlasses(streamId, bmpData, totalChunks, bmpChunkSize)
1207
+ currentImageChunks.clear()
1208
+ currentImageChunks.addAll(chunks)
1209
+ sendDataSequentially(chunks)
1210
+
1211
+ // Note: The following are commented out in the original
1212
+ // sendBmpEndCommand()
1213
+ // sendBmpCRC(bmpData)
1214
+ // lastThingDisplayedWasAnImage = true
1215
+
1216
+ } catch (e: Exception) {
1217
+ Log.e(TAG,"Error in displayBitmapImage: ${e.message}")
1218
+ }
1219
+ }
1220
+
1221
+ private fun stopPeriodicNotifications() {
1222
+ notificationRunnable?.let { runnable ->
1223
+ notificationHandler?.removeCallbacks(runnable)
1224
+ Bridge.log("Stopped periodic notifications")
1225
+ }
1226
+ }
1227
+ private fun startMicBeat(delay: Int) {
1228
+ Bridge.log("Nex: Starting micbeat")
1229
+ if (micBeatCount > 0) {
1230
+ stopMicBeat()
1231
+ }
1232
+ sendSetMicEnabled(true, 10)
1233
+ micBeatRunnable = Runnable {
1234
+ Bridge.log("Nex: SENDING MIC BEAT")
1235
+ sendSetMicEnabled(shouldUseGlassesMic, 1)
1236
+ micBeatHandler.postDelayed(micBeatRunnable!!, MICBEAT_INTERVAL_MS)
1237
+ }
1238
+ micBeatHandler.postDelayed(micBeatRunnable!!, delay.toLong())
1239
+ }
1240
+
1241
+ private fun stopMicBeat() {
1242
+ sendSetMicEnabled(false, 10)
1243
+ if (micBeatHandler != null) {
1244
+ micBeatHandler.removeCallbacksAndMessages(null);
1245
+ micBeatHandler.removeCallbacksAndMessages(micBeatRunnable);
1246
+ micBeatRunnable = null;
1247
+ micBeatCount = 0;
1248
+ }
1249
+ }
1250
+
1251
+ private fun sendWhiteListCommand(delay: Int) {
1252
+ if (whiteListedAlready) return
1253
+
1254
+ whiteListedAlready = true
1255
+ Bridge.log("Nex: Sending whitelist command")
1256
+
1257
+ whiteListHandler.postDelayed({
1258
+ val chunks = NexProtobufUtils.getWhitelistChunks(maxChunkSize)
1259
+ sendDataSequentially(chunks)
1260
+
1261
+ // Uncomment if needed for debugging:
1262
+ // chunks.forEach { chunk ->
1263
+ // Bridge.log("Nex: Sending this chunk for white list: ${bytesToUtf8(chunk)}")
1264
+ // sendDataSequentially(chunk)
1265
+ // // Thread.sleep(150) // Uncomment if delay between chunks is needed
1266
+ // }
1267
+ }, delay.toLong())
1268
+ }
1269
+
1270
+ private fun showHomeScreen() {
1271
+ Bridge.log("Nex: showHomeScreen")
1272
+
1273
+ if (lastThingDisplayedWasAnImage) {
1274
+ // clearNexScreen()
1275
+ lastThingDisplayedWasAnImage = false
1276
+ }
1277
+ }
1278
+
1279
+ private fun updateConnectionState() {
1280
+ if (isMainConnected) {
1281
+ DeviceStore.apply("glasses", "connectionState", ConnTypes.CONNECTED)
1282
+ Bridge.log("Nex: Main glasses connected")
1283
+ lastConnectionTimestamp = System.currentTimeMillis()
1284
+ DeviceStore.apply("glasses", "fullyBooted", true)
1285
+ DeviceStore.apply("glasses", "connected", true)
1286
+ // Removed commented sleep code as it's not needed
1287
+ // connectionEvent(it)
1288
+ } else {
1289
+ DeviceStore.apply("glasses", "connectionState", ConnTypes.DISCONNECTED)
1290
+ Bridge.log("Nex: No Main glasses connected")
1291
+ DeviceStore.apply("glasses", "fullyBooted", false)
1292
+ DeviceStore.apply("glasses", "connected", false)
1293
+ // connectionEvent(it)
1294
+ }
1295
+ }
1296
+
1297
+ private fun decodeJsons(jsonBytes: ByteArray) {
1298
+ val jsonString = String(jsonBytes, Charset.defaultCharset())
1299
+ try {
1300
+ val commandObject = JSONObject(jsonString)
1301
+ when (val type = commandObject.getString("type")) {
1302
+ "image_transfer_complete" -> {
1303
+ // Handle image transfer complete
1304
+ }
1305
+ "disconnect" -> {
1306
+ // Handle disconnect
1307
+ }
1308
+ "request_battery_state" -> {
1309
+ // Handle battery state request
1310
+ }
1311
+ "charging_state" -> {
1312
+ // Handle charging state
1313
+ }
1314
+ "device_info" -> {
1315
+ // val deviceInfo = gson.fromJson(jsonString, DeviceInfo::class.java)
1316
+ }
1317
+ "enter_pairing_mode" -> {
1318
+ // Handle pairing mode
1319
+ }
1320
+ "request_head_position" -> {
1321
+ // Handle head position request
1322
+ }
1323
+ "set_head_up_angle" -> {
1324
+ // Handle head up angle
1325
+ }
1326
+ "ping" -> {
1327
+ // Handle ping
1328
+ }
1329
+ "vad_event" -> {
1330
+ // Handle VAD event
1331
+ }
1332
+ "imu_data" -> {
1333
+ // Handle IMU data
1334
+ }
1335
+ "button_event" -> {
1336
+ // Handle button event
1337
+ }
1338
+ "head_gesture" -> {
1339
+ // Handle head gesture
1340
+ }
1341
+ }
1342
+ } catch (e: Exception) {
1343
+ Log.e(TAG,"Error decoding JSON: ${e.message}")
1344
+ }
1345
+ }
1346
+
1347
+ private fun decodeProtobufs(protobufBytes: ByteArray, packetHex: String) {
1348
+ try {
1349
+ val glassesToPhone = GlassesToPhone.parseFrom(protobufBytes)
1350
+ val payloadCase = glassesToPhone.payloadCase.toString()
1351
+
1352
+ Bridge.log("decodeProtobufs glassesToPhone: $glassesToPhone")
1353
+ Bridge.log("decodeProtobufs glassesToPhone payloadCase: $payloadCase")
1354
+
1355
+ NexEventUtils.sendBleCommandReceivedEvent(payloadCase, packetHex, System.currentTimeMillis())
1356
+ // if (isDebugMode) {
1357
+ // EventBus.getDefault().post(BleCommandReceiver(payloadCase, packetHex))
1358
+ // }
1359
+
1360
+ when (glassesToPhone.payloadCase) {
1361
+ GlassesToPhone.PayloadCase.BATTERY_STATUS -> {
1362
+ val batteryStatus: BatteryStatus = glassesToPhone.batteryStatus
1363
+ DeviceStore.apply("glasses", "batteryLevel", batteryStatus.level)
1364
+ // EventBus.getDefault().post(BatteryLevelEvent(batteryStatus.level, batteryStatus.charging))
1365
+ Bridge.log("batteryStatus: $batteryStatus")
1366
+ }
1367
+ GlassesToPhone.PayloadCase.CHARGING_STATE -> {
1368
+ val chargingState: ChargingState = glassesToPhone.chargingState
1369
+ // EventBus.getDefault().post(BatteryLevelEvent(batteryLevel, chargingState.state == State.CHARGING))
1370
+ Bridge.log("chargingState: $chargingState")
1371
+ }
1372
+ GlassesToPhone.PayloadCase.DEVICE_INFO -> {
1373
+ val deviceInfo: DeviceInfo = glassesToPhone.deviceInfo
1374
+ Bridge.log("deviceInfo: $deviceInfo")
1375
+ }
1376
+ GlassesToPhone.PayloadCase.HEAD_POSITION -> {
1377
+ val headPosition: HeadPosition = glassesToPhone.headPosition
1378
+ // EventBus.getDefault().post(HeadUpAngleEvent(headPosition.angle))
1379
+ Bridge.log("headPosition: $headPosition")
1380
+ }
1381
+ GlassesToPhone.PayloadCase.HEAD_UP_ANGLE_SET -> {
1382
+ val headUpAngleResponse: HeadUpAngleResponse = glassesToPhone.headUpAngleSet
1383
+ Bridge.log("headUpAngleResponse: $headUpAngleResponse")
1384
+ }
1385
+ GlassesToPhone.PayloadCase.PING -> {
1386
+ lastHeartbeatReceivedTime = System.currentTimeMillis()
1387
+ Bridge.log("=== RECEIVED PING FROM GLASSES === (Time: $lastHeartbeatReceivedTime)")
1388
+ sendPongResponse()
1389
+ }
1390
+ GlassesToPhone.PayloadCase.VAD_EVENT -> {
1391
+ // val vadEvent = glassesToPhone.vadEvent
1392
+ // EventBus.getDefault().post(VadEvent(vadEvent.vad))
1393
+ }
1394
+ GlassesToPhone.PayloadCase.IMAGE_TRANSFER_COMPLETE -> {
1395
+ val transferComplete: ImageTransferComplete = glassesToPhone.imageTransferComplete
1396
+ Bridge.log("transferComplete: $transferComplete")
1397
+
1398
+ when (transferComplete.status) {
1399
+ ImageTransferComplete.Status.OK -> {
1400
+ currentImageChunks.clear()
1401
+ isImageSendProgressing = false
1402
+ }
1403
+ ImageTransferComplete.Status.INCOMPLETE -> {
1404
+ val missingChunksList = transferComplete.missingChunksList
1405
+ reSendImageMissingChunks(missingChunksList)
1406
+ }
1407
+ else -> {}
1408
+ }
1409
+ }
1410
+ GlassesToPhone.PayloadCase.IMU_DATA -> {
1411
+ val imuData: ImuData = glassesToPhone.imuData
1412
+ Bridge.log("imuData: $imuData")
1413
+ }
1414
+ GlassesToPhone.PayloadCase.BUTTON_EVENT -> {
1415
+ val buttonEvent: ButtonEvent = glassesToPhone.buttonEvent
1416
+ Bridge.log("buttonEvent: $buttonEvent")
1417
+ // buttonEvent.button.number
1418
+ // EventBus.getDefault().post(ButtonPressEvent(
1419
+ // smartGlassesDevice.deviceModelName,
1420
+ // buttonId,
1421
+ // pressType,
1422
+ // timestamp
1423
+ // ))
1424
+ }
1425
+ GlassesToPhone.PayloadCase.HEAD_GESTURE -> {
1426
+ val headGesture: HeadGesture = glassesToPhone.headGesture
1427
+ Bridge.log("headGesture: $headGesture")
1428
+ // EventBus.getDefault().post(GlassesHeadUpEvent())
1429
+ // EventBus.getDefault().post(GlassesHeadDownEvent())
1430
+ // EventBus.getDefault().post(GlassesTapOutputEvent(2, isRight, System.currentTimeMillis()))
1431
+ }
1432
+ GlassesToPhone.PayloadCase.VERSION_RESPONSE -> {
1433
+ val versionResponse: VersionResponse = glassesToPhone.versionResponse
1434
+ Bridge.log("=== RECEIVED GLASSES PROTOBUF VERSION RESPONSE ===")
1435
+ Bridge.log("Glasses Protobuf Version: ${versionResponse.version}")
1436
+ Bridge.log("Message ID: ${versionResponse.msgId}")
1437
+ DeviceStore.apply("glasses", "protobufVersion", versionResponse.version.toString())
1438
+
1439
+ if (versionResponse.commit.isNotEmpty()) {
1440
+ Bridge.log("Commit: ${versionResponse.commit}")
1441
+ }
1442
+ if (versionResponse.buildDate.isNotEmpty()) {
1443
+ Bridge.log("Build Date: ${versionResponse.buildDate}")
1444
+ }
1445
+
1446
+ // Post glasses protobuf version event to update UI
1447
+ // EventBus.getDefault().post(ProtocolVersionResponseEvent(
1448
+ // versionResponse.version,
1449
+ // versionResponse.commit,
1450
+ // versionResponse.buildDate,
1451
+ // versionResponse.msgId
1452
+ // ))
1453
+ }
1454
+ GlassesToPhone.PayloadCase.PAYLOAD_NOT_SET,
1455
+ null -> {
1456
+ // Do nothing
1457
+ }
1458
+ else -> {
1459
+
1460
+ }
1461
+ }
1462
+ } catch (e: InvalidProtocolBufferException) {
1463
+ Log.e(TAG,"Error decoding protobuf: ${e.message}")
1464
+ }
1465
+ }
1466
+
1467
+ private fun decodeProtobufsByWrite(protobufBytes: ByteArray, packetHex: String) {
1468
+ try {
1469
+ val phoneToGlasses = PhoneToGlasses.parseFrom(protobufBytes)
1470
+ Bridge.log("decodeProtobufsByWrite phoneToGlasses: $phoneToGlasses")
1471
+ Bridge.log("decodeProtobufsByWrite phoneToGlasses payloadCase: ${phoneToGlasses.payloadCase}")
1472
+ val payloadCase = phoneToGlasses.payloadCase.toString()
1473
+ NexEventUtils.sendBleCommandSentEvent(payloadCase, packetHex, System.currentTimeMillis())
1474
+ // if (isDebugMode) {
1475
+ // EventBus.getDefault().post(BleCommandSender(payloadCase, packetHex))
1476
+ // }
1477
+ } catch (e: Exception) {
1478
+ Log.e(TAG,"Error in decodeProtobufsByWrite: ${e.message}")
1479
+ }
1480
+ }
1481
+
1482
+ private fun reSendImageMissingChunks(missingChunksIndexList: List<Int>) {
1483
+ if (!isImageSendProgressing || currentImageChunks.isEmpty() || missingChunksIndexList.isEmpty()) {
1484
+ return
1485
+ }
1486
+ val missingChunks = missingChunksIndexList.map { index ->
1487
+ currentImageChunks[index]
1488
+ }
1489
+
1490
+ sendDataSequentially(missingChunks)
1491
+ }
1492
+
1493
+ private fun sendPongResponse() {
1494
+ // Respond to ping from glasses with pong
1495
+ lastHeartbeatReceivedTime = System.currentTimeMillis()
1496
+ Bridge.log("=== SENDING PONG RESPONSE TO GLASSES === (Time: $lastHeartbeatReceivedTime)")
1497
+
1498
+ val pongPacket = NexProtobufUtils.constructPongResponse()
1499
+
1500
+ // Send the pong response
1501
+ if (pongPacket != null) {
1502
+ sendDataSequentially(pongPacket, 100)
1503
+ Bridge.log("Pong response sent successfully")
1504
+
1505
+ // Notify mobile app about pong sent
1506
+ lastHeartbeatSentTime = System.currentTimeMillis()
1507
+ NexEventUtils.sendHeartbeatSentEvent(lastHeartbeatSentTime)
1508
+ // EventBus.getDefault().post(HeartbeatSentEvent(timestamp))
1509
+ } else {
1510
+ Log.e(TAG,"Failed to construct pong response packet")
1511
+ }
1512
+
1513
+ // Still query battery periodically (every 10 pings received)
1514
+ if (batteryLevel == -1 || heartbeatCount % 10 == 0) {
1515
+ mainTaskHandler.sendEmptyMessageDelayed(MAIN_TASK_HANDLER_CODE_BATTERY_QUERY, 500)
1516
+ }
1517
+
1518
+ heartbeatCount++
1519
+
1520
+ // Notify mobile app about heartbeat received
1521
+ NexEventUtils.sendHeartbeatReceivedEvent(lastHeartbeatReceivedTime)
1522
+ // EventBus.getDefault().post(HeartbeatReceivedEvent(lastHeartbeatReceivedTime))
1523
+ }
1524
+
1525
+ private fun createTextWallChunksForNex(text: String): ByteArray {
1526
+ // Create the PhoneToGlasses using its builder and set the DisplayText
1527
+ return NexProtobufUtils.generateDisplayTextCommandBytes(text)
1528
+ }
1529
+
1530
+ private fun sendSetMicEnabled(enable: Boolean, delay: Long) {
1531
+ Bridge.log("Nex: setMicEnabled called with enable: $enable and delay: $delay")
1532
+ Bridge.log("Nex: Running set mic enabled: $enable")
1533
+ isMicrophoneEnabled = enable // Update the state tracker
1534
+ DeviceStore.apply("glasses", "micEnabled", enable)
1535
+
1536
+ micEnableHandler?.postDelayed({
1537
+ if (connectionState != ConnTypes.CONNECTED) {
1538
+ Bridge.log("Nex: Tryna start mic: Not connected to glasses")
1539
+ return@postDelayed
1540
+ }
1541
+ Bridge.log("Nex: === SENDING MICROPHONE STATE COMMAND TO GLASSES ===")
1542
+ val micConfigBytes = NexProtobufUtils.generateMicStateConfigCommandBytes(enable)
1543
+ sendDataSequentially(micConfigBytes, 10) // wait some time to setup the mic
1544
+ Bridge.log("Nex: Sent MIC command: ${micConfigBytes.joinToString("") { "%02x".format(it) }}")
1545
+ }, delay)
1546
+ }
1547
+
1548
+ private fun queryBatteryStatus() {
1549
+ val batteryQueryPacket = NexProtobufUtils.generateBatteryStateRequestCommandBytes()
1550
+ sendDataSequentially(batteryQueryPacket, 250)
1551
+ }
1552
+
1553
+ ///// PROCESSING THREAD /////////////////
1554
+
1555
+ // Start the worker thread if it's not already running
1556
+ @Synchronized
1557
+ private fun startWorkerIfNeeded() {
1558
+ if (!isWorkerRunning) {
1559
+ isWorkerRunning = true
1560
+ Thread({ processQueue() }, "MentraNexSGCProcessQueue").start()
1561
+ }
1562
+ }
1563
+
1564
+ // Non-blocking function to add new send request
1565
+ private fun sendDataSequentially(data: ByteArray) {
1566
+ val chunks = arrayOf(SendRequest(data))
1567
+ sendQueue.offer(chunks)
1568
+ startWorkerIfNeeded()
1569
+ }
1570
+
1571
+ // Non-blocking function to add new send request with wait time
1572
+ private fun sendDataSequentially(data: ByteArray, waitTime: Int) {
1573
+ val chunks = arrayOf(SendRequest(data, waitTime))
1574
+ sendQueue.offer(chunks)
1575
+ startWorkerIfNeeded()
1576
+ }
1577
+
1578
+ private fun sendDataSequentially(data: List<ByteArray>) {
1579
+ val chunks = Array(data.size) { i -> SendRequest(data[i]) }
1580
+ sendQueue.offer(chunks)
1581
+ startWorkerIfNeeded()
1582
+ }
1583
+
1584
+ private fun processQueue() {
1585
+ // First wait until the services are ready to receive data
1586
+ Bridge.log("[BLE] PROC_QUEUE started — waiting for descriptor write (mainServicesWaiter)")
1587
+ try {
1588
+ mainServicesWaiter.waitWhileTrue()
1589
+ } catch (e: InterruptedException) {
1590
+ Log.e(TAG,"Nex: Interrupted waiting for descriptor writes: $e")
1591
+ }
1592
+ Bridge.log("[BLE] PROC_QUEUE ready — descriptor write done, entering send loop")
1593
+
1594
+ while (!isKilled) {
1595
+ try {
1596
+ // Make sure services are ready before processing requests
1597
+ mainServicesWaiter.waitWhileTrue()
1598
+
1599
+ // This will block until data is available
1600
+ Bridge.log("[BLE] PROC_QUEUE waiting for data in sendQueue (size=${sendQueue.size})")
1601
+ val requests = sendQueue.take()
1602
+ Bridge.log("[BLE] PROC_QUEUE dequeued ${requests.size} request(s)")
1603
+
1604
+ for (request in requests) {
1605
+ if (isKilled) {
1606
+ isWorkerRunning = false
1607
+ break
1608
+ }
1609
+
1610
+ try {
1611
+ // Force an initial delay so BLE gets all setup
1612
+ val timeSinceConnection = System.currentTimeMillis() - lastConnectionTimestamp
1613
+ if (timeSinceConnection < INITIAL_CONNECTION_DELAY_MS) {
1614
+ Thread.sleep(INITIAL_CONNECTION_DELAY_MS - timeSinceConnection)
1615
+ }
1616
+
1617
+ // Send to main glass
1618
+ val canSend = mainGlassGatt != null && mainWriteChar != null && isMainConnected
1619
+ Bridge.log("[BLE] PROC_QUEUE send check: gatt=${mainGlassGatt != null} writeChar=${mainWriteChar != null} connected=$isMainConnected → canSend=$canSend len=${request.data.size}")
1620
+ if (canSend) {
1621
+ mainWaiter.setTrue()
1622
+ mainWriteChar?.value = request.data
1623
+ mainGlassGatt?.writeCharacteristic(mainWriteChar)
1624
+ lastSendTimestamp = System.currentTimeMillis()
1625
+ Bridge.log("[BLE] PROC_QUEUE writeCharacteristic issued, waiting for onCharacteristicWrite callback")
1626
+ } else {
1627
+ Bridge.log("[BLE] PROC_QUEUE skipping write — not ready, packet dropped")
1628
+ }
1629
+
1630
+ mainWaiter.waitWhileTrue()
1631
+
1632
+ Thread.sleep(DELAY_BETWEEN_CHUNKS_SEND)
1633
+
1634
+ // If the packet asked us to do a delay, then do it
1635
+ if (request.waitTime != -1) {
1636
+ Thread.sleep(request.waitTime.toLong())
1637
+ }
1638
+ } catch (e: InterruptedException) {
1639
+ Log.e(TAG,"Nex: Error sending data: ${e.message}")
1640
+ if (isKilled) break
1641
+ }
1642
+ }
1643
+ } catch (e: InterruptedException) {
1644
+ if (isKilled) {
1645
+ Bridge.log("Nex: Process queue thread interrupted - shutting down")
1646
+ break
1647
+ }
1648
+ Log.e(TAG,"Nex: Error in queue processing: ${e.message}")
1649
+ }
1650
+ }
1651
+
1652
+ Bridge.log("Nex: Process queue thread exiting")
1653
+ isWorkerRunning = false
1654
+ }
1655
+ }