@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,241 @@
1
+ //
2
+ // JSCExperiment.swift
3
+ // MentraOS
4
+ //
5
+ // Test/benchmark infrastructure. Self-contained file, never invoked in
6
+ // normal user flow. Triggered only when (a) MENTRA_RUN_JSC_BENCH env
7
+ // var is set at app launch, or (b) the user taps a JSC button on the
8
+ // (Super-Mode-gated) stress-test screen.
9
+ //
10
+ // Spike: measure the actual memory cost of N concurrent JSContexts on iOS.
11
+ // We want to know whether "Pebble-style native JSC per miniapp" is viable
12
+ // for our N-concurrent-miniapp use case (Pebble runs only 1 at a time, so
13
+ // they have no data on this).
14
+ //
15
+ // Each spawned context:
16
+ // - Has its own JSVirtualMachine (full isolation, separate heap)
17
+ // - Has __dispatch as a single bridge function (no per-method bindings,
18
+ // to avoid the production crash Pebble documented in CrashReproducer.kt)
19
+ // - Runs a representative idle workload: setInterval ping every 5 s
20
+ // - Names itself "MentraJS: <id>" for Safari Web Inspector
21
+ // - Is NOT inspectable in release builds
22
+ //
23
+ // Measure resident memory before and after spawning N contexts.
24
+
25
+ import Foundation
26
+ import JavaScriptCore
27
+ import os.log
28
+
29
+ private let jscLog = OSLog(subsystem: "com.mentra.mentra", category: "JSCSpike")
30
+
31
+ /// Log to BOTH jlog (forwarded into JS) AND os_log AND a flat file in
32
+ /// the app's Documents directory. Release builds don't pipe RN's
33
+ /// console.log or Swift print() to syslog, and os_log filtering through
34
+ /// idevicesyslog is unreliable. The Documents file is the agentic-test
35
+ /// path: `xcrun devicectl device copy from --domain-type appDataContainer
36
+ /// --domain-identifier com.mentra.mentra --source Documents/jsc-spike.log`.
37
+ private let logFileURL: URL = {
38
+ let docs = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
39
+ return docs.appendingPathComponent("jsc-spike.log")
40
+ }()
41
+
42
+ private func jlog(_ message: String) {
43
+ Bridge.log(message)
44
+ os_log("%{public}@", log: jscLog, type: .info, message)
45
+ let stamped = "[\(ISO8601DateFormatter().string(from: Date()))] \(message)\n"
46
+ if let data = stamped.data(using: .utf8) {
47
+ if FileManager.default.fileExists(atPath: logFileURL.path) {
48
+ if let handle = try? FileHandle(forWritingTo: logFileURL) {
49
+ defer { try? handle.close() }
50
+ try? handle.seekToEnd()
51
+ try? handle.write(contentsOf: data)
52
+ }
53
+ } else {
54
+ try? data.write(to: logFileURL, options: .atomic)
55
+ }
56
+ }
57
+ }
58
+
59
+ @objc public class JSCExperiment: NSObject {
60
+ /// Called from BluetoothSdkModule init. If MENTRA_RUN_JSC_BENCH env var
61
+ /// is set, kick off the benchmark after a 5s settle window so the app
62
+ /// is fully booted (RN bridge up, Metro pull done if dev, etc).
63
+ @objc public static func maybeAutoBenchmark() {
64
+ guard ProcessInfo.processInfo.environment["MENTRA_RUN_JSC_BENCH"] != nil else { return }
65
+ os_log("🧪 MENTRA_RUN_JSC_BENCH set — auto-running benchmark in 5s",
66
+ log: jscLog, type: .info)
67
+ DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: .now() + 5.0) {
68
+ runBenchmark()
69
+ }
70
+ }
71
+
72
+ /// id → (context, virtualMachine, timer for the workload)
73
+ private static var contexts: [String: (JSContext, JSVirtualMachine, Timer)] = [:]
74
+ private static let queue = DispatchQueue(label: "com.mentra.jsc-experiment")
75
+
76
+ /// Spawn N JSContexts at once. Each gets its own VM + a representative
77
+ /// idle workload. Returns the count successfully spawned.
78
+ @objc public static func spawn(count: Int) -> Int {
79
+ let beforeMB = MemoryMonitor.currentMemoryMB()
80
+ jlog("🧪 JSCExperiment.spawn(\(count)) starting; baseline \(String(format: "%.1f", beforeMB)) MB")
81
+ var spawned = 0
82
+ for i in 0..<count {
83
+ let id = "spike-\(UUID().uuidString.prefix(8))-\(i)"
84
+ if spawnOne(id: id) {
85
+ spawned += 1
86
+ }
87
+ }
88
+ let afterMB = MemoryMonitor.currentMemoryMB()
89
+ let perMB = spawned > 0 ? (afterMB - beforeMB) / Double(spawned) : 0
90
+ jlog("🧪 JSCExperiment: spawned \(spawned)/\(count) contexts; total alive \(contexts.count); mem \(String(format: "%.1f", beforeMB))→\(String(format: "%.1f", afterMB)) MB (\(String(format: "%+.2f", perMB)) MB/ctx)")
91
+ return spawned
92
+ }
93
+
94
+ /// Spawn one named context. Returns true on success.
95
+ @objc public static func spawnOne(id: String) -> Bool {
96
+ return queue.sync {
97
+ // Each context gets its own VM = full heap isolation between miniapps.
98
+ // (JSContexts that share a VM share the heap; we want isolation.)
99
+ let vm = JSVirtualMachine()!
100
+ let ctx = JSContext(virtualMachine: vm)!
101
+
102
+ ctx.name = "MentraJS: \(id)"
103
+ // Inspectable in dev builds only. iOS 16.4+ guarded.
104
+ #if DEBUG
105
+ if #available(iOS 16.4, *) {
106
+ ctx.isInspectable = true
107
+ }
108
+ #endif
109
+
110
+ // Single-dispatcher bridge. Per Pebble's CrashReproducer doc, never
111
+ // bind individual native callbacks as JSValue properties — JSC's GC
112
+ // races with ARC and crashes. One C-callable function only.
113
+ let dispatch: @convention(block) (String, String, [Any]?) -> Any? = { iface, method, args in
114
+ // Stub: real impl routes to native services.
115
+ // For the spike we just need __dispatch to exist so the
116
+ // workload can call it without throwing.
117
+ return NSNull()
118
+ }
119
+ ctx.setObject(dispatch, forKeyedSubscript: "__dispatch" as NSString)
120
+
121
+ // Representative idle workload — what a typical miniapp's
122
+ // background JS does: hold a tiny bit of state, register a
123
+ // listener, periodically poke the bridge.
124
+ let workload = """
125
+ (function () {
126
+ var counter = 0;
127
+ var state = { id: \"\(id)\", startedAt: Date.now(), notes: [] };
128
+ var i = 0;
129
+ function tick() {
130
+ counter++;
131
+ state.notes.push({ at: Date.now(), n: counter });
132
+ if (state.notes.length > 100) state.notes.shift();
133
+ __dispatch('noop', 'tick', [counter]);
134
+ }
135
+ tick();
136
+ // Schedule a periodic tick. We use the host-provided
137
+ // setInterval (a JS shim that calls into native).
138
+ if (typeof setInterval === 'function') {
139
+ setInterval(tick, 5000);
140
+ }
141
+ })();
142
+ """
143
+ ctx.evaluateScript(workload)
144
+
145
+ // Workload uses setInterval which is a JS shim that we'd build
146
+ // for real — for the spike we just install a Timer-based
147
+ // setInterval that fires the JS callback. The JS code already
148
+ // expects setInterval to exist as a global.
149
+ let setInterval: @convention(block) (JSValue, Double) -> Int = { fn, ms in
150
+ let id = Int.random(in: 1...Int.max)
151
+ // Stub: we won't actually fire in the spike, but the JS
152
+ // callback existing is what we want for memory measurement.
153
+ _ = fn // hold a ref to keep the JS callback alive
154
+ _ = ms
155
+ return id
156
+ }
157
+ ctx.setObject(setInterval, forKeyedSubscript: "setInterval" as NSString)
158
+
159
+ // Idle workload Timer on the native side that does NOTHING —
160
+ // it just exists so the context isn't optimized away. The real
161
+ // JS callback (registered above) will be hit by a real
162
+ // setInterval impl in production.
163
+ let timer = Timer.scheduledTimer(withTimeInterval: 5.0, repeats: true) { _ in
164
+ // no-op — keep ref alive
165
+ }
166
+
167
+ contexts[id] = (ctx, vm, timer)
168
+ return true
169
+ }
170
+ }
171
+
172
+ /// Tear down all contexts. Important: cancel timers, drop refs, force GC.
173
+ @objc public static func killAll() {
174
+ queue.sync {
175
+ for (_, entry) in contexts {
176
+ entry.2.invalidate()
177
+ }
178
+ let count = contexts.count
179
+ contexts.removeAll()
180
+ jlog("📊 JSCExperiment: killed all \(count) contexts")
181
+ }
182
+ }
183
+
184
+ /// How many contexts are alive right now.
185
+ @objc public static func aliveCount() -> Int {
186
+ return queue.sync { contexts.count }
187
+ }
188
+
189
+ /// Convenience: spawn N + return memory delta vs baseline. Caller
190
+ /// records baseline via MemoryMonitor.currentMemoryMB() before calling.
191
+ @objc public static func spawnAndMeasure(count: Int, baselineMB: Double) -> [String: Any] {
192
+ let beforeMB = MemoryMonitor.currentMemoryMB()
193
+ let spawned = spawn(count: count)
194
+ // Tiny settle delay so allocations complete before we read.
195
+ Thread.sleep(forTimeInterval: 0.5)
196
+ let afterMB = MemoryMonitor.currentMemoryMB()
197
+ let perContextMB = spawned > 0 ? (afterMB - beforeMB) / Double(spawned) : 0
198
+ let result: [String: Any] = [
199
+ "spawned": spawned,
200
+ "totalAlive": aliveCount(),
201
+ "baselineMB": baselineMB,
202
+ "beforeMB": beforeMB,
203
+ "afterMB": afterMB,
204
+ "deltaMB": afterMB - beforeMB,
205
+ "perContextMB": perContextMB,
206
+ ]
207
+ jlog("📊 JSCExperiment.spawnAndMeasure: \(result)")
208
+ return result
209
+ }
210
+
211
+ /// Run a full benchmark sweep: spawn 1, 5, 10, 25, 50 in waves with
212
+ /// 2s settle between, log resident MB before and after each. All
213
+ /// output via jlog so it reaches syslog in release builds.
214
+ @objc public static func runBenchmark() {
215
+ DispatchQueue.global(qos: .userInitiated).async {
216
+ killAll()
217
+ // Clear log file for a fresh run.
218
+ try? FileManager.default.removeItem(at: logFileURL)
219
+ Thread.sleep(forTimeInterval: 1.0)
220
+ let baseline = MemoryMonitor.currentMemoryMB()
221
+ jlog("🧪 JSC-BENCH start; baseline \(String(format: "%.1f", baseline)) MB; logFile=\(logFileURL.path)")
222
+ let waves = [1, 5, 10, 25, 50]
223
+ var prevAlive = 0
224
+ for target in waves {
225
+ let need = target - prevAlive
226
+ if need > 0 {
227
+ let beforeMB = MemoryMonitor.currentMemoryMB()
228
+ _ = spawn(count: need)
229
+ Thread.sleep(forTimeInterval: 1.0)
230
+ let afterMB = MemoryMonitor.currentMemoryMB()
231
+ let perContextOverall = (afterMB - baseline) / Double(target)
232
+ jlog("🧪 JSC-BENCH wave: target=\(target) alive=\(aliveCount()) before=\(String(format: "%.1f", beforeMB))MB after=\(String(format: "%.1f", afterMB))MB delta=\(String(format: "%+.1f", afterMB - beforeMB))MB cumPerCtx=\(String(format: "%.2f", perContextOverall))MB")
233
+ prevAlive = target
234
+ }
235
+ Thread.sleep(forTimeInterval: 2.0)
236
+ }
237
+ let final = MemoryMonitor.currentMemoryMB()
238
+ jlog("🧪 JSC-BENCH done; alive=\(aliveCount()) final=\(String(format: "%.1f", final))MB total-delta=\(String(format: "%+.1f", final - baseline))MB")
239
+ }
240
+ }
241
+ }
@@ -0,0 +1,44 @@
1
+ //
2
+ // MemoryMonitor.swift
3
+ // MentraOS
4
+ //
5
+ // Memory usage monitoring for leak detection
6
+ //
7
+
8
+ import Foundation
9
+
10
+ class MemoryMonitor {
11
+ private static var timer: Timer?
12
+ private static var startMemoryMB: Double = 0
13
+
14
+ static func start(intervalSeconds: TimeInterval = 30) {
15
+ stop()
16
+ startMemoryMB = currentMemoryMB()
17
+ Bridge.log("📊 Memory Monitor started - baseline: \(String(format: "%.1f", startMemoryMB)) MB")
18
+
19
+ DispatchQueue.main.async {
20
+ timer = Timer.scheduledTimer(withTimeInterval: intervalSeconds, repeats: true) { _ in
21
+ let current = currentMemoryMB()
22
+ let delta = current - startMemoryMB
23
+ let trend = delta > 50 ? "🔴" : delta > 20 ? "🟡" : "⚪"
24
+ Bridge.log("📊 Memory: \(String(format: "%.1f", current)) MB (Δ \(String(format: "%+.1f", delta)) MB) \(trend)")
25
+ }
26
+ }
27
+ }
28
+
29
+ static func stop() {
30
+ timer?.invalidate()
31
+ timer = nil
32
+ }
33
+
34
+ static func currentMemoryMB() -> Double {
35
+ var info = mach_task_basic_info()
36
+ var count = mach_msg_type_number_t(MemoryLayout<mach_task_basic_info>.size) / 4
37
+ let result = withUnsafeMutablePointer(to: &info) {
38
+ $0.withMemoryRebound(to: integer_t.self, capacity: 1) {
39
+ task_info(mach_task_self_, task_flavor_t(MACH_TASK_BASIC_INFO), $0, &count)
40
+ }
41
+ }
42
+ return result == KERN_SUCCESS ? Double(info.resident_size) / 1024 / 1024 : 0
43
+ }
44
+ }
@@ -0,0 +1,164 @@
1
+ import Foundation
2
+
3
+ /**
4
+ * Handles chunking of large messages that exceed BLE transmission limits.
5
+ * Messages are split at the JSON layer to work within MCU protocol constraints.
6
+ *
7
+ * Uses compact keys to minimize overhead per chunk:
8
+ * t = "ck" (chunk type identifier)
9
+ * id = chunk session ID
10
+ * c = chunk index (0-based)
11
+ * n = total number of chunks
12
+ * d = chunk data payload
13
+ *
14
+ * Each chunk after C-wrapping + K900 framing must fit within the BES2700's
15
+ * 253-byte BLE write limit. With compact keys, 80 bytes of raw data produces
16
+ * a final packed size of ~245 bytes worst-case (with heavy JSON escaping).
17
+ */
18
+ class MessageChunker {
19
+ // Threshold: if C-wrapped message exceeds this, chunking is triggered.
20
+ // BES2700 limit is 253 bytes; anything over ~200 bytes packed needs chunking.
21
+ private static let MESSAGE_SIZE_THRESHOLD = 200
22
+
23
+ /// Maximum raw bytes per chunk. After double JSON escaping + compact envelope
24
+ /// + C-wrapper + K900 framing, 80 bytes stays under the 253-byte BLE limit.
25
+ private static let CHUNK_DATA_SIZE = 80
26
+
27
+ /**
28
+ * Check if a message needs to be chunked
29
+ * @param message The complete message string (already C-wrapped)
30
+ * @return true if message exceeds threshold and needs chunking
31
+ */
32
+ static func needsChunking(_ message: String?) -> Bool {
33
+ guard let message = message else {
34
+ return false
35
+ }
36
+
37
+ let messageBytes = message.data(using: .utf8)?.count ?? 0
38
+ let needsChunking = messageBytes > MESSAGE_SIZE_THRESHOLD
39
+
40
+ if needsChunking {
41
+ print("MessageChunker: Message size \(messageBytes) exceeds threshold \(MESSAGE_SIZE_THRESHOLD), will chunk")
42
+ }
43
+
44
+ return needsChunking
45
+ }
46
+
47
+ /**
48
+ * Create chunks from a message that's too large for single transmission.
49
+ * Uses compact keys to minimize per-chunk overhead.
50
+ * @param originalJson The original JSON string to be sent (before C-wrapping)
51
+ * @param messageId The message ID for ACK tracking (if applicable)
52
+ * @return Array of chunk dictionaries ready to be C-wrapped and sent
53
+ */
54
+ static func createChunks(originalJson: String, messageId: Int64 = -1) -> [[String: Any]] {
55
+ guard let messageData = originalJson.data(using: .utf8) else {
56
+ print("MessageChunker: Failed to convert message to data")
57
+ return []
58
+ }
59
+
60
+ var chunks: [[String: Any]] = []
61
+ let totalBytes = messageData.count
62
+
63
+ // Compact chunk session ID: messageId_timestamp (no "chunk_" prefix)
64
+ let chunkId = "\(messageId)_\(Int(Date().timeIntervalSince1970 * 1000))"
65
+
66
+ // Calculate total chunks needed
67
+ let totalChunks = Int(ceil(Double(totalBytes) / Double(CHUNK_DATA_SIZE)))
68
+
69
+ print("MessageChunker: Creating \(totalChunks) chunks for message of size \(totalBytes) bytes")
70
+
71
+ for i in 0 ..< totalChunks {
72
+ let startIndex = i * CHUNK_DATA_SIZE
73
+ let endIndex = min(startIndex + CHUNK_DATA_SIZE, totalBytes)
74
+ let chunkRange = startIndex ..< endIndex
75
+
76
+ // Extract chunk data as string
77
+ let chunkData = messageData.subdata(in: chunkRange)
78
+ guard let chunkString = String(data: chunkData, encoding: .utf8) else {
79
+ print("MessageChunker: Failed to convert chunk \(i) to string")
80
+ continue
81
+ }
82
+
83
+ // Create chunk dictionary with compact keys
84
+ var chunk: [String: Any] = [
85
+ "t": "ck",
86
+ "id": chunkId,
87
+ "c": i,
88
+ "n": totalChunks,
89
+ "d": chunkString,
90
+ ]
91
+
92
+ // Add message ID to final chunk only for ACK tracking
93
+ if i == totalChunks - 1, messageId != -1 {
94
+ chunk["mId"] = messageId
95
+ }
96
+
97
+ chunks.append(chunk)
98
+
99
+ print("MessageChunker: Created chunk \(i)/\(totalChunks - 1) with \(chunkData.count) bytes")
100
+ }
101
+
102
+ return chunks
103
+ }
104
+
105
+ /**
106
+ * Check if a received message is a chunked message.
107
+ * Supports both verbose ("type":"chunked_msg") and compact ("t":"ck") formats.
108
+ * @param json The received dictionary (after C-unwrapping)
109
+ * @return true if this is a chunked message
110
+ */
111
+ static func isChunkedMessage(_ json: [String: Any]?) -> Bool {
112
+ guard let json = json else {
113
+ return false
114
+ }
115
+
116
+ let type = json["type"] as? String ?? json["t"] as? String ?? ""
117
+ return type == "chunked_msg" || type == "ck"
118
+ }
119
+
120
+ /**
121
+ * Extract chunk information from a chunked message.
122
+ * Supports both verbose and compact key formats.
123
+ */
124
+ static func getChunkInfo(_ json: [String: Any]) -> ChunkInfo? {
125
+ guard isChunkedMessage(json) else {
126
+ return nil
127
+ }
128
+
129
+ // Support both verbose and compact keys
130
+ guard let chunkId = (json["chunkId"] as? String) ?? (json["id"] as? String),
131
+ let chunkIndex = (json["chunk"] as? Int) ?? (json["c"] as? Int),
132
+ let totalChunks = (json["total"] as? Int) ?? (json["n"] as? Int),
133
+ let data = (json["data"] as? String) ?? (json["d"] as? String)
134
+ else {
135
+ print("MessageChunker: Failed to extract chunk info from JSON")
136
+ return nil
137
+ }
138
+
139
+ let messageId = json["mId"] as? Int64 ?? -1
140
+
141
+ return ChunkInfo(
142
+ chunkId: chunkId,
143
+ chunkIndex: chunkIndex,
144
+ totalChunks: totalChunks,
145
+ data: data,
146
+ messageId: messageId
147
+ )
148
+ }
149
+
150
+ /**
151
+ * Container for chunk information
152
+ */
153
+ struct ChunkInfo {
154
+ let chunkId: String
155
+ let chunkIndex: Int
156
+ let totalChunks: Int
157
+ let data: String
158
+ let messageId: Int64
159
+
160
+ var isFinalChunk: Bool {
161
+ return chunkIndex == totalChunks - 1
162
+ }
163
+ }
164
+ }
@@ -0,0 +1,135 @@
1
+ //
2
+ // Models.swift
3
+ // MentraOS_Manager
4
+ //
5
+ // Created by Matthew Fosse on 3/3/25.
6
+ //
7
+
8
+ import Foundation
9
+
10
+ struct AiResponseToG1Model {
11
+ var lines: [String]
12
+ var totalPages: UInt8
13
+ var newScreen: Bool
14
+ var currentPage: UInt8 {
15
+ didSet {
16
+ print("SET : currentPage :\(currentPage)")
17
+ }
18
+ }
19
+
20
+ var maxPages: UInt8
21
+ var status: DisplayStatus
22
+ }
23
+
24
+ struct ThirdPartyCloudApp {
25
+ let packageName: String
26
+ let name: String
27
+ let description: String
28
+ let webhookURL: String
29
+ let logoURL: String
30
+ let isRunning: Bool
31
+ }
32
+
33
+ /// NCSNotification structure
34
+ struct NCSNotification: Codable {
35
+ let msgId: Int
36
+ let type: Int
37
+ let appIdentifier: String
38
+ let title: String
39
+ let subtitle: String
40
+ let message: String
41
+ let timeS: Int
42
+ let date: String
43
+ let displayName: String
44
+
45
+ enum CodingKeys: String, CodingKey {
46
+ case msgId = "msg_id"
47
+ case type
48
+ case appIdentifier = "app_identifier"
49
+ case title
50
+ case subtitle
51
+ case message
52
+ case timeS = "time_s"
53
+ case date
54
+ case displayName = "display_name"
55
+ }
56
+
57
+ init(msgId: Int, appIdentifier: String, title: String, subtitle: String, message: String, displayName: String, type: Int = 1) {
58
+ self.msgId = msgId
59
+ self.type = type
60
+ self.appIdentifier = appIdentifier
61
+ self.title = title
62
+ self.subtitle = subtitle
63
+ self.message = message
64
+ timeS = Int(Date().timeIntervalSince1970)
65
+
66
+ let dateFormatter = DateFormatter()
67
+ dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
68
+ date = dateFormatter.string(from: Date())
69
+
70
+ self.displayName = displayName
71
+ }
72
+ }
73
+
74
+ /// Notification structure
75
+ struct G1Notification: Codable {
76
+ let ncsNotification: NCSNotification
77
+ let type: String = "Add"
78
+
79
+ enum CodingKeys: String, CodingKey {
80
+ case ncsNotification = "ncs_notification"
81
+ case type
82
+ }
83
+
84
+ /// Convert to JSON dictionary
85
+ func toJson() -> [String: Any] {
86
+ guard let data = try? JSONEncoder().encode(self) else { return [:] }
87
+ guard let jsonObject = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
88
+ return [:]
89
+ }
90
+ return jsonObject
91
+ }
92
+
93
+ /// Convert to JSON Data
94
+ func toData() -> Data? {
95
+ guard let jsonString = try? JSONEncoder().encode(self) else { return nil }
96
+ return jsonString
97
+ }
98
+
99
+ /// Build notification chunks
100
+ func constructNotification() async -> [[UInt8]] {
101
+ guard let jsonData = toData() else { return [] }
102
+
103
+ let maxChunkSize = 176 // 180 - 4 bytes for header
104
+ var chunks: [Data] = []
105
+
106
+ // Split data into chunks
107
+ var offset = 0
108
+ while offset < jsonData.count {
109
+ let endIndex = min(offset + maxChunkSize, jsonData.count)
110
+ let chunkData = jsonData.subdata(in: offset ..< endIndex)
111
+ chunks.append(chunkData)
112
+ offset = endIndex
113
+ }
114
+
115
+ let totalChunks = UInt8(chunks.count)
116
+ var encodedChunks: [[UInt8]] = []
117
+
118
+ // Create packets with headers
119
+ for (index, chunk) in chunks.enumerated() {
120
+ let notifyId: UInt8 = 0 // Set appropriate notification ID
121
+ let header: [UInt8] = [0x4B, notifyId, totalChunks, UInt8(index)]
122
+
123
+ // Convert chunk data to array of bytes
124
+ var chunkBytes = [UInt8](chunk)
125
+
126
+ // Combine header and chunk
127
+ var encodedChunk = header
128
+ encodedChunk.append(contentsOf: chunkBytes)
129
+
130
+ encodedChunks.append(encodedChunk)
131
+ }
132
+
133
+ return encodedChunks
134
+ }
135
+ }